๐Ÿ” Number Reverser & Array Merger โ€” Mini Projects
0%
Mini Projects  ยท  Pointers  ยท  Arrays

๐Ÿ” Number Reverser
& Array Merger

Two tightly connected mini projects โ€” both built entirely with pointers and arrays, zero structs. Project 1 teaches the two-pointer technique. Project 2 teaches pointer-driven merging of two sorted arrays into one. Both are fundamental algorithms you will use forever.

P1
๐Ÿ” Number Reverser
Reverse an int array in-place using two pointers โ€” left and right โ€” walking toward each other. No extra array. O(n/2) swaps.
two-pointer in-place swap pointer walk
P2
๐Ÿ”€ Array Merger
Merge two sorted int arrays into a single sorted result array using three pointers โ€” one per array. O(m+n) time, one pass.
three pointers sorted merge pointer compare
Project 1 ๐Ÿ” Number Reverser โ€” Two-Pointer In-Place Reversal
The core idea โ€” two pointers, walking inward. Place one pointer at the start of the array (left) and one at the end (right). Swap the values they point at. Move left one step right, move right one step left. Repeat until they meet or cross. The array is reversed in exactly n/2 swaps, using zero extra memory โ€” no temporary array, no second copy. Just two pointers and a single temp variable for the swap.
two-pointer reversal โ€” step by step on array [10, 20, 30, 40, 50]
Start Step 1 Step 2 Done 10 20 30 40 50 L R swap(L,R) 50 20 30 40 10 L R swap(L,R) 50 40 30 20 10 L==R ยท stop [ 50 40 30 20 10 ] โœ“ reversed
ConceptHow usedCode
int *leftPoints to first element, walks rightint *left = arr
int *rightPoints to last element, walks leftint *right = arr + n - 1
SwapTemp variable, deref both pointersint t = *left; *left = *right; *right = t
AdvanceMove pointers inward each iterationleft++; right--
Stop conditionPointers meet or crosswhile(left < right)
p1 โ€” step 1 ยท basic reverse
S1
๐Ÿ”„ reverse() โ€” The Two-Pointer Swap Core
int *left = arr, int *right = arr+n-1 โ€” swap while left < right โ€” in-place, zero extra memory
Core Algorithm
The two-pointer technique is one of the most important patterns in programming. Two pointers start at opposite ends and walk toward each other. At each step they swap what they point at. When they meet or cross, every element has been swapped exactly once โ€” the array is fully reversed. The loop condition while(left < right) handles both even-length arrays (pointers meet exactly in the middle) and odd-length arrays (they cross without meeting, leaving the middle element untouched โ€” correct, since the middle element needs no swap).
p1_s1_reverse_core.c
C
#include <stdio.h>

/* Print array via pointer walk */
void printArr(const int *arr, int n, const char *label) {
    printf("  %-10s [ ", label);
    const int *p = arr;
    while (p < arr + n) printf("%d ", *p++);
    printf("]\n");
}

/* Core reverse โ€” two-pointer in-place */
void reverse(int *arr, int n) {
    int *left  = arr;           /* start pointer โ€” arr[0]   */
    int *right = arr + n - 1;  /* end pointer   โ€” arr[n-1] */

    while (left < right) {      /* stop when pointers meet  */
        int tmp = *left;          /* 1. save left value       */
        *left   = *right;         /* 2. copy right โ†’ left     */
        *right  = tmp;            /* 3. copy saved โ†’ right    */
        left++;                   /* 4. left moves inward โ–ถ   */
        right--;                  /* 5. right moves inward โ—€  */
    }
}

int main() {
    int a[] = {10, 20, 30, 40, 50};   /* odd length  */
    int b[] = {5, 15, 25, 35};         /* even length */
    int c[] = {99};                      /* single element */

    printf("=== Odd-length array (5 elements) ===\n");
    printArr(a, 5, "Before:");
    reverse(a, 5);
    printArr(a, 5, "After :");

    printf("\n=== Even-length array (4 elements) ===\n");
    printArr(b, 4, "Before:");
    reverse(b, 4);
    printArr(b, 4, "After :");

    printf("\n=== Single element ===\n");
    printArr(c, 1, "Before:");
    reverse(c, 1);
    printArr(c, 1, "After :");
    return 0;
}
output
=== Odd-length array (5 elements) ===
  Before:    [ 10 20 30 40 50 ]
  After :    [ 50 40 30 20 10 ]

=== Even-length array (4 elements) ===
  Before:    [ 5 15 25 35 ]
  After :    [ 35 25 15 5 ]

=== Single element ===
  Before:    [ 99 ]
  After :    [ 99 ]
Why while(left < right) not left <= right? With <=, an odd-length array would try to swap the middle element with itself โ€” harmless but pointless. With <, the middle element is skipped cleanly. Both produce the correct result, but < is more efficient and the idiomatic C choice.
p1 โ€” step 2 ยท verbose trace + char reverse
S2
๐Ÿ”Ž Verbose Trace + String Reversal โ€” Same Algorithm, Different Type
Print every swap step โ€” then apply identical logic to char arrays
Trace + Char
The two-pointer reversal is type-agnostic โ€” the same logic works on any array type. By changing int* to char* and int tmp to char tmp, the identical algorithm reverses a string in-place. We also add a verbose version that prints every swap step โ€” when you can see each pointer position and swap, the algorithm becomes completely transparent.
p1_s2_trace_string.c
C
#include <stdio.h>
#include <string.h>

/* Verbose reverse โ€” prints every swap step */
void reverseVerbose(int *arr, int n) {
    int *left  = arr;
    int *right = arr + n - 1;
    int  step  = 1;

    printf("  Step  Left[idx]  Right[idx]  Action\n");
    printf("  ----------------------------------------\n");

    while (left < right) {
        printf("   %2d   arr[%td]=%d   arr[%td]=%d   swap\n",
               step,
               left  - arr, *left,
               right - arr, *right);

        int tmp = *left;
        *left   = *right;
        *right  = tmp;
        left++; right--; step++;
    }
    printf("       Pointers met โ€” done.\n");
}

/* Char reversal โ€” identical logic, different type */
void reverseString(char *s) {
    char *left  = s;
    char *right = s + strlen(s) - 1;
    while (left < right) {
        char tmp = *left;
        *left    = *right;
        *right   = tmp;
        left++; right--;
    }
}

int main() {
    int arr[] = {3, 7, 1, 9, 4, 6};
    printf("=== Verbose Trace ===\n");
    reverseVerbose(arr, 6);
    printf("  Result: [ ");
    for(int i=0;i<6;i++) printf("%d ",arr[i]);
    printf("]\n");

    printf("\n=== String Reversal (char array) ===\n");
    char words[][15] = {"Ananta", "pointer", "racecar"};
    for(int i=0;i<3;i++){
        printf("  %-10s  โ†’  ", words[i]);
        reverseString(words[i]);
        printf("%s\n", words[i]);
    }
    return 0;
}
output
=== Verbose Trace ===
  Step  Left[idx]  Right[idx]  Action
  ----------------------------------------
    1   arr[0]=3   arr[5]=6   swap
    2   arr[1]=7   arr[4]=4   swap
    3   arr[2]=1   arr[3]=9   swap
       Pointers met โ€” done.
  Result: [ 6 4 9 1 7 3 ]

=== String Reversal (char array) ===
  Ananta     โ†’  atnanA
  pointer    โ†’  retniop
  racecar    โ†’  racecar
Pointer subtraction left - arr gives the index of the element left currently points at. If arr is at address 1000 and int is 4 bytes, then left at address 1008 gives (1008-1000)/4 = 2 โ€” index 2. C performs this division automatically when you subtract two pointers of the same type.
p1 โ€” step 3 ยท complete reverser
S3
๐Ÿ Complete Number Reverser โ€” All Variants Together
Reverse, partial reverse, check palindrome, reverse sub-range โ€” all via two-pointer
Full Project 1
Four variants of the same core algorithm show how versatile two-pointer reversal is. Full reverse โ€” the base case. Partial reverse โ€” reverse only indices [from..to] by setting left and right within that sub-range. Palindrome check โ€” compare without swapping: walk inward, return false if any pair mismatches. Rotate array โ€” reverse the whole, then reverse first k, then reverse rest โ€” a beautiful three-reversal trick.
p1_complete_reverser.c
C โ€” Complete Project 1
#include <stdio.h>

void printArr(const int *a, int n){
    printf("[ ");
    const int *p=a;
    while(p<a+n) printf("%d ",*p++);
    printf("]");
}

/* 1. Full reverse โ€” O(n/2) swaps */
void reverse(int *a, int n) {
    int *L=a, *R=a+n-1;
    while(L<R){
        int t=*L; *L=*R; *R=t;
        L++; R--;
    }
}

/* 2. Partial reverse โ€” reverse only [from..to] */
void reverseRange(int *a, int from, int to) {
    int *L=a+from, *R=a+to;
    while(L<R){
        int t=*L; *L=*R; *R=t;
        L++; R--;
    }
}

/* 3. Palindrome check โ€” two-pointer compare (no swap) */
int isPalindrome(const int *a, int n) {
    const int *L=a, *R=a+n-1;
    while(L<R){
        if(*L != *R) return 0;  /* mismatch โ€” not palindrome */
        L++; R--;
    }
    return 1;
}

/* 4. Rotate left by k โ€” three-reversal trick
      Rotate [1,2,3,4,5] left by 2 โ†’ [3,4,5,1,2]
      Step 1: reverse full   โ†’ [5,4,3,2,1]
      Step 2: reverse [n-k..n-1] โ†’ [5,4,3,1,2] โ€” wrong order
      Correct: reverse [0..k-1], reverse [k..n-1], reverse all */
void rotateLeft(int *a, int n, int k) {
    k = k % n;                    /* handle k >= n */
    reverseRange(a, 0,   k-1);  /* reverse first k   */
    reverseRange(a, k,   n-1);  /* reverse rest      */
    reverseRange(a, 0,   n-1);  /* reverse entire    */
}

int main() {
    printf("=== 1. Full Reverse ===\n");
    int a[] = {10,20,30,40,50};
    printf("  Before: "); printArr(a,5);
    reverse(a,5);
    printf("  After : "); printArr(a,5); printf("\n");

    printf("\n=== 2. Partial Reverse (index 1 to 3) ===\n");
    int b[] = {1,2,3,4,5};
    printf("  Before: "); printArr(b,5);
    reverseRange(b,1,3);
    printf("  After : "); printArr(b,5); printf("\n");

    printf("\n=== 3. Palindrome Check ===\n");
    int p1[] = {1,2,3,2,1};
    int p2[] = {1,2,3,4,5};
    printf("  [1,2,3,2,1]: %s\n", isPalindrome(p1,5)?"Palindrome":"Not palindrome");
    printf("  [1,2,3,4,5]: %s\n", isPalindrome(p2,5)?"Palindrome":"Not palindrome");

    printf("\n=== 4. Rotate Left by 2 ===\n");
    int r[] = {1,2,3,4,5};
    printf("  Before: "); printArr(r,5);
    rotateLeft(r,5,2);
    printf("  After : "); printArr(r,5); printf("\n");
    return 0;
}
output
=== 1. Full Reverse ===
  Before: [ 10 20 30 40 50 ]  After : [ 50 40 30 20 10 ]

=== 2. Partial Reverse (index 1 to 3) ===
  Before: [ 1 2 3 4 5 ]  After : [ 1 4 3 2 5 ]

=== 3. Palindrome Check ===
  [1,2,3,2,1]: Palindrome
  [1,2,3,4,5]: Not palindrome

=== 4. Rotate Left by 2 ===
  Before: [ 1 2 3 4 5 ]  After : [ 3 4 5 1 2 ]
The three-reversal rotation trick is a classic algorithm that rotates an array in O(n) time and O(1) space using only the reversal function you already wrote. Rotating left by k: reverse first k elements, reverse remaining n-k elements, then reverse the entire array. Three calls to the same function โ€” elegant reuse of one primitive operation.
Project 2 ๐Ÿ”€ Array Merger โ€” Three-Pointer Sorted Merge
The core idea โ€” three pointers, one pass. Two sorted input arrays, one output array. Three pointers: p1 walks array A, p2 walks array B, out writes into the result. At each step, compare *p1 and *p2 โ€” whichever is smaller goes into *out, and its pointer advances. When one array is exhausted, copy the remaining elements of the other. Total time: O(m + n) โ€” every element is looked at exactly once.
three-pointer merge โ€” [1,3,5] and [2,4,6] โ†’ [1,2,3,4,5,6]
Step Array A (p1โ†’) Array B (p2โ†’) Result (outโ†’) Action 0 1 3 5 2 4 6 [empty] 1 < 2 โ†’ write 1, p1++ 1 โœ“ 3 5 2 4 6 1 2 < 3 โ†’ write 2, p2++ 2 3 โœ“ 4 1 2 3 < 4 โ†’ write 3, p1++ โ€ฆ continues: 4, 5, 6 written similarly Result: [ 1 2 3 4 5 6 ] โœ“ sorted, one pass, O(m+n)
p2 โ€” step 1 ยท core merge
S1
๐Ÿ”€ merge() โ€” Three Pointers, One Comparison, One Pass
p1 walks A, p2 walks B, out writes result โ€” smaller wins each step
Core Merge
The merge function receives two sorted arrays and an output array. Three pointers are initialised โ€” p1 = A, p2 = B, out = result. In each iteration of the main loop, compare *p1 and *p2. The smaller value is written to *out, out advances, and the pointer whose value was chosen advances. When either input array is exhausted, the remaining elements of the other are copied in bulk โ€” they are already sorted so no further comparisons needed.
p2_s1_merge_core.c
C
#include <stdio.h>

void printArr(const int *a,int n,const char *lbl){
    printf("  %-10s [ ",lbl);
    const int *p=a;
    while(p<a+n) printf("%d ",*p++);
    printf("]\n");
}

/* Core merge โ€” three-pointer sorted merge
   A[0..m-1] and B[0..n-1] must both be sorted
   result must have space for m+n elements      */
void merge(const int *A, int m,
            const int *B, int n,
            int *result) {

    const int *p1   = A;          /* walks array A */
    const int *p2   = B;          /* walks array B */
    int       *out  = result;     /* writes result */
    const int *endA = A + m;      /* one-past-last of A */
    const int *endB = B + n;      /* one-past-last of B */

    /* Main loop โ€” both arrays have elements */
    while (p1 < endA && p2 < endB) {
        if (*p1 <= *p2)
            *out++ = *p1++;    /* A wins โ€” write A, advance p1 and out */
        else
            *out++ = *p2++;    /* B wins โ€” write B, advance p2 and out */
    }

    /* Copy remaining elements of A (if any) */
    while (p1 < endA) *out++ = *p1++;

    /* Copy remaining elements of B (if any) */
    while (p2 < endB) *out++ = *p2++;
}

int main() {
    int A[] = {1, 3, 5, 7, 9};
    int B[] = {2, 4, 6, 8};
    int result[9];

    printf("=== Basic Merge ===\n");
    printArr(A, 5, "Array A:");
    printArr(B, 4, "Array B:");
    merge(A, 5, B, 4, result);
    printArr(result, 9, "Merged :");

    printf("\n=== One array much larger ===\n");
    int C[] = {10, 20, 30, 40, 50, 60};
    int D[] = {25};
    int r2[7];
    printArr(C, 6, "Array C:");
    printArr(D, 1, "Array D:");
    merge(C, 6, D, 1, r2);
    printArr(r2, 7, "Merged :");

    printf("\n=== Arrays with duplicates ===\n");
    int E[] = {1, 2, 2, 5};
    int F[] = {2, 3, 5, 6};
    int r3[8];
    printArr(E, 4, "Array E:");
    printArr(F, 4, "Array F:");
    merge(E, 4, F, 4, r3);
    printArr(r3, 8, "Merged :");
    return 0;
}
output
=== Basic Merge ===
  Array A:   [ 1 3 5 7 9 ]
  Array B:   [ 2 4 6 8 ]
  Merged :   [ 1 2 3 4 5 6 7 8 9 ]

=== One array much larger ===
  Array C:   [ 10 20 30 40 50 60 ]
  Array D:   [ 25 ]
  Merged :   [ 10 20 25 30 40 50 60 ]

=== Arrays with duplicates ===
  Array E:   [ 1 2 2 5 ]
  Array F:   [ 2 3 5 6 ]
  Merged :   [ 1 2 2 2 3 5 5 6 ]
*out++ = *p1++ โ€” post-increment on both sides. This reads *p1, writes it to *out, then increments both pointers. It is the most compact way to write "copy current element and advance." Equivalent to *out = *p1; out++; p1++; โ€” three lines compressed to one idiomatic C expression.
p2 โ€” step 2 ยท verbose + union + intersection
S2
๐Ÿ”Ž Verbose Merge + Union + Intersection โ€” Three-Pointer Variants
Same three-pointer frame โ€” skip duplicates for union โ€” only keep matches for intersection
Variants
The three-pointer pattern is more powerful than simple merging. With small changes to the comparison logic you get entirely different operations โ€” union (all unique elements from both arrays) and intersection (only elements that appear in both). Both still run in O(m+n) time with the same three-pointer structure, proving that the frame is reusable and the logic swap is the only change.

Union โ€” skip duplicates

When *p1 == *p2, write one copy and advance both pointers. Eliminates duplicates from the output.

Intersection โ€” only matches

When *p1 == *p2, write it and advance both. When unequal, advance the pointer with the smaller value โ€” skipping the non-match.

p2_s2_union_intersection.c
C
#include <stdio.h>

void printArr(const int*a,int n,const char*l){
    printf("  %-15s[ ",l);
    const int*p=a;
    while(p<a+n) printf("%d ",*p++);
    printf("]\n");
}

/* Union โ€” all unique elements from both sorted arrays */
int unionArr(const int*A,int m,const int*B,int n,int*R){
    const int *p1=A,*p2=B,*eA=A+m,*eB=B+n;
    int *out=R;
    while(p1<eA && p2<eB){
        if      (*p1 < *p2) *out++=*p1++;   /* A smaller โ€” take A    */
        else if (*p2 < *p1) *out++=*p2++;   /* B smaller โ€” take B    */
        else { *out++=*p1++; p2++; }         /* equal โ€” take one, skip both */
    }
    while(p1<eA) *out++=*p1++;
    while(p2<eB) *out++=*p2++;
    return out - R;   /* return count of elements written */
}

/* Intersection โ€” only elements present in both arrays */
int intersection(const int*A,int m,const int*B,int n,int*R){
    const int *p1=A,*p2=B,*eA=A+m,*eB=B+n;
    int *out=R;
    while(p1<eA && p2<eB){
        if      (*p1 < *p2) p1++;            /* A smaller โ€” skip A    */
        else if (*p2 < *p1) p2++;            /* B smaller โ€” skip B    */
        else { *out++=*p1++; p2++; }         /* match โ€” write and skip both */
    }
    return out - R;
}

int main() {
    int A[] = {1,2,3,5,7,9};
    int B[] = {2,3,4,6,7,8};
    int R[12];
    int cnt;

    printf("Input:\n");
    printArr(A,6,"Array A:");
    printArr(B,6,"Array B:");

    printf("\n=== Union (all unique) ===\n");
    cnt = unionArr(A,6,B,6,R);
    printArr(R,cnt,"Union:");

    printf("\n=== Intersection (common only) ===\n");
    cnt = intersection(A,6,B,6,R);
    printArr(R,cnt,"Intersection:");
    return 0;
}
output
Input:
  Array A:       [ 1 2 3 5 7 9 ]
  Array B:       [ 2 3 4 6 7 8 ]

=== Union (all unique) ===
  Union:         [ 1 2 3 4 5 6 7 8 9 ]

=== Intersection (common only) ===
  Intersection:  [ 2 3 7 ]
Return count via pointer subtraction: return out - R โ€” out ended up pointing past the last written element. Subtracting the start pointer R gives the exact number of elements written. No counter variable needed โ€” the pointer position is the count.
p2 โ€” step 3 ยท complete merger program
S3
๐Ÿ Complete Array Merger โ€” Sort, Merge, Union, Intersect
Unsorted input โ†’ sort first โ†’ merge โ†’ union โ†’ intersection โ€” full pipeline
Full Project 2
A complete pipeline: accept unsorted arrays, sort them first (using pointer-based bubble sort), then run all three operations โ€” merge, union, intersection โ€” in sequence. This shows how individual pointer functions compose into a real data processing pipeline. Every function uses only pointers and arrays โ€” no structs, no file I/O, just the fundamentals.
array_merger_complete.c โ€” Complete Project 2
C
#include <stdio.h>

/* โ”€โ”€ PRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void printArr(const int*a,int n,const char*l){
    printf("  %-18s[ ",l);
    const int*p=a;
    while(p<a+n) printf("%3d ",*p++);
    printf("]\n");
}

/* โ”€โ”€ SORT โ€” pointer-based bubble sort โ”€โ”€โ”€โ”€โ”€ */
void sortArr(int*a,int n){
    for(int i=0;i<n-1;i++)
        for(int j=0;j<n-i-1;j++)
            if(a[j]>a[j+1]){
                int*L=a+j,*R=a+j+1;
                int t=*L;*L=*R;*R=t;   /* pointer swap */
            }
}

/* โ”€โ”€ MERGE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
int merge(const int*A,int m,const int*B,int n,int*R){
    const int*p1=A,*p2=B,*eA=A+m,*eB=B+n;
    int*out=R;
    while(p1<eA&&p2<eB)
        *out++= (*p1<=*p2) ? *p1++ : *p2++;
    while(p1<eA)*out++=*p1++;
    while(p2<eB)*out++=*p2++;
    return out-R;
}

/* โ”€โ”€ UNION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
int unionArr(const int*A,int m,const int*B,int n,int*R){
    const int*p1=A,*p2=B,*eA=A+m,*eB=B+n;
    int*out=R;
    while(p1<eA&&p2<eB){
        if     (*p1<*p2) *out++=*p1++;
        else if(*p2<*p1) *out++=*p2++;
        else{*out++=*p1++;p2++;}
    }
    while(p1<eA)*out++=*p1++;
    while(p2<eB)*out++=*p2++;
    return out-R;
}

/* โ”€โ”€ INTERSECTION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
int intersection(const int*A,int m,const int*B,int n,int*R){
    const int*p1=A,*p2=B,*eA=A+m,*eB=B+n;
    int*out=R;
    while(p1<eA&&p2<eB){
        if     (*p1<*p2) p1++;
        else if(*p2<*p1) p2++;
        else{*out++=*p1++;p2++;}
    }
    return out-R;
}

/* โ”€โ”€ REVERSE โ€” two-pointer โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
void reverse(int*a,int n){
    int*L=a,*R=a+n-1;
    while(L<R){int t=*L;*L=*R;*R=t;L++;R--;}
}

/* โ”€โ”€ MAIN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
int main() {
    int A[] = {9,3,7,1,5,3};   /* unsorted */
    int B[] = {8,2,6,4,3,10};  /* unsorted */
    int R[12]; int cnt;

    printf("=== INPUT (unsorted) ===\n");
    printArr(A,6,"Array A:");
    printArr(B,6,"Array B:");

    printf("\n=== STEP 1 โ€” Sort both ===\n");
    sortArr(A,6); sortArr(B,6);
    printArr(A,6,"Sorted A:");
    printArr(B,6,"Sorted B:");

    printf("\n=== STEP 2 โ€” Merge (all elements) ===\n");
    cnt=merge(A,6,B,6,R);
    printArr(R,cnt,"Merged:");

    printf("\n=== STEP 3 โ€” Union (no duplicates) ===\n");
    cnt=unionArr(A,6,B,6,R);
    printArr(R,cnt,"Union:");

    printf("\n=== STEP 4 โ€” Intersection (common) ===\n");
    cnt=intersection(A,6,B,6,R);
    printArr(R,cnt,"Intersect:");

    printf("\n=== STEP 5 โ€” Reverse the merged result ===\n");
    cnt=merge(A,6,B,6,R);
    reverse(R,cnt);
    printArr(R,cnt,"Reversed:");
    return 0;
}
output
=== INPUT (unsorted) ===
  Array A:          [   9   3   7   1   5   3 ]
  Array B:          [   8   2   6   4   3  10 ]

=== STEP 1 โ€” Sort both ===
  Sorted A:         [   1   3   3   5   7   9 ]
  Sorted B:         [   2   3   4   6   8  10 ]

=== STEP 2 โ€” Merge (all elements) ===
  Merged:           [   1   2   3   3   3   4   5   6   7   8   9  10 ]

=== STEP 3 โ€” Union (no duplicates) ===
  Union:            [   1   2   3   4   5   6   7   8   9  10 ]

=== STEP 4 โ€” Intersection (common) ===
  Intersect:        [   3 ]

=== STEP 5 โ€” Reverse the merged result ===
  Reversed:         [  10   9   8   7   6   5   4   3   3   3   2   1 ]
The last step reuses Project 1. After merging, we call reverse(R, cnt) โ€” the same two-pointer function from Project 1 โ€” to reverse the merged result. This shows how small, focused pointer functions compose: sort โ†’ merge โ†’ reverse is a complete data pipeline built from three independent functions, each under 10 lines.
checklist โ€” tick each concept when understood
  • P1 Core: Two-pointer reversal โ€” int *L = arr; int *R = arr+n-1. Swap via int t=*L; *L=*R; *R=t. Advance with L++; R--. Stop when L < R. Exactly n/2 swaps. Zero extra memory.
  • P1 Pointer subtraction: left - arr gives current index. Pointer arithmetic scales by sizeof automatically. Works for any pointer type of the same array.
  • P1 Type-agnostic: Change int* to char* and int tmp to char tmp โ€” identical logic reverses a string. The algorithm is the same; only the type changes.
  • P1 reverseRange: int *L = arr+from; int *R = arr+to โ€” partial reverse of any sub-range. Foundation of the three-reversal rotation trick.
  • P1 Palindrome: Same two-pointer frame but no swap โ€” compare *L != *R and return 0 on mismatch. Demonstrates the pattern is reusable beyond swapping.
  • P2 Core merge: Three pointers โ€” p1=A, p2=B, out=result. Compare *p1 vs *p2, write smaller, advance that pointer. Copy remainder when one array exhausted. O(m+n) โ€” one pass.
  • P2 *out++ = *p1++: Post-increment on both sides โ€” read, write, advance in one expression. Equivalent to three separate lines. Most idiomatic C array-copy pattern.
  • P2 Union: When *p1 == *p2, write one and skip both. Removes duplicates from merged output. Same three-pointer frame, one logic change.
  • P2 Intersection: When equal write both + advance, when unequal advance the smaller. Only common elements reach the output. O(m+n) โ€” same frame again.
  • Composition: sort โ†’ merge โ†’ reverse chains Project 2 functions with Project 1's reverse. Small focused functions compose into pipelines โ€” the C way of building programs.