functions in C practice search transformations statistics arrays Fedora gcc

Functions in C practice — 3 real programs that consolidate pass by reference

In the previous article we covered C functions theory. Now it’s time to write real programs. In this article we build three programs from scratch in gedit and compile them with gcc in Fedora — array search, transformations by reference and a statistics calculator. Each one puts pass by value and pass by reference to work in situations where the difference is not just theoretical.

Set up your workspace:

cd ~/GCID/IC2/Labs
mkdir Lab_functions
cd Lab_functions

Functions in C practice — Program 1: Array search functions

This program implements a complete set of search functions for integer arrays. All read-only searches pass arrays as const int * — this protects the original data and signals the intent clearly.

touch search.c
gedit search.c &
#include <stdio.h>

/* Prototypes */
int linear_search(const int *arr, int n, int target);
int binary_search(const int *arr, int n, int target);
int find_minimum(const int *arr, int n, int *index);
int find_maximum(const int *arr, int n, int *index);
int count_occurrences(const int *arr, int n, int target);
void find_range(const int *arr, int n, int *min, int *max);
int is_sorted(const int *arr, int n);
void print_array(const int *arr, int n);

/* Linear search — O(n), works on unsorted arrays */
int linear_search(const int *arr, int n, int target) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == target)
            return i;    /* return index of first match */
    }
    return -1;    /* not found */
}

/* Binary search — O(log n), requires SORTED array */
int binary_search(const int *arr, int n, int target) {
    int left = 0, right = n - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;    /* avoids overflow */

        if (arr[mid] == target)
            return mid;
        if (arr[mid] < target)
            left = mid + 1;     /* target is in right half */
        else
            right = mid - 1;    /* target is in left half */
    }
    return -1;    /* not found */
}

/* Find minimum — returns value, stores index through pointer */
int find_minimum(const int *arr, int n, int *index) {
    if (n <= 0) return 0;
    int min = arr[0];
    *index = 0;
    for (int i = 1; i < n; i++) {
        if (arr[i] < min) {
            min = arr[i];
            *index = i;
        }
    }
    return min;
}

/* Find maximum — same pattern as find_minimum */
int find_maximum(const int *arr, int n, int *index) {
    if (n <= 0) return 0;
    int max = arr[0];
    *index = 0;
    for (int i = 1; i < n; i++) {
        if (arr[i] > max) {
            max = arr[i];
            *index = i;
        }
    }
    return max;
}

/* Count how many times target appears */
int count_occurrences(const int *arr, int n, int target) {
    int count = 0;
    for (int i = 0; i < n; i++)
        if (arr[i] == target)
            count++;
    return count;
}

/* Fill min and max through pointers — two results at once */
void find_range(const int *arr, int n, int *min, int *max) {
    *min = arr[0];
    *max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
    }
}

/* Check if array is sorted in ascending order */
int is_sorted(const int *arr, int n) {
    for (int i = 0; i < n - 1; i++)
        if (arr[i] > arr[i+1])
            return 0;    /* 0 = false */
    return 1;    /* 1 = true */
}

void print_array(const int *arr, int n) {
    printf("[");
    for (int i = 0; i < n; i++) {
        printf("%d", arr[i]);
        if (i < n - 1) printf(", ");
    }
    printf("]");
}

/* Main program */
int main() {
    int unsorted[] = {64, 34, 25, 12, 22, 11, 90};
    int sorted[]   = {11, 12, 22, 25, 34, 64, 90};
    int n = 7;

    printf("=== ARRAY SEARCH FUNCTIONS ===\n\n");

    printf("Unsorted: ");
    print_array(unsorted, n);
    printf("\nSorted:   ");
    print_array(sorted, n);
    printf("\n\n");

    /* Linear search — works on both sorted and unsorted */
    int targets[] = {25, 64, 99};
    printf("--- Linear search ---\n");
    for (int t = 0; t < 3; t++) {
        int idx = linear_search(unsorted, n, targets[t]);
        if (idx >= 0)
            printf("  %2d found at index %d\n", targets[t], idx);
        else
            printf("  %2d not found\n", targets[t]);
    }

    /* Binary search — requires sorted array */
    printf("\n--- Binary search (sorted array) ---\n");
    for (int t = 0; t < 3; t++) {
        int idx = binary_search(sorted, n, targets[t]);
        if (idx >= 0)
            printf("  %2d found at index %d\n", targets[t], idx);
        else
            printf("  %2d not found\n", targets[t]);
    }

    /* Min and max with index */
    printf("\n--- Min and Max ---\n");
    int min_idx, max_idx;
    int minimum = find_minimum(unsorted, n, &min_idx);
    int maximum = find_maximum(unsorted, n, &max_idx);
    printf("  Minimum: %d at index %d\n", minimum, min_idx);
    printf("  Maximum: %d at index %d\n", maximum, max_idx);

    /* Range */
    int range_min, range_max;
    find_range(unsorted, n, &range_min, &range_max);
    printf("  Range:   %d to %d\n", range_min, range_max);

    /* Count occurrences */
    int with_dupes[] = {3, 7, 3, 2, 7, 7, 1, 3};
    int m = 8;
    printf("\n--- Count occurrences ---\n");
    printf("  Array: ");
    print_array(with_dupes, m);
    printf("\n");
    printf("  3 appears %d times\n", count_occurrences(with_dupes, m, 3));
    printf("  7 appears %d times\n", count_occurrences(with_dupes, m, 7));
    printf("  9 appears %d times\n", count_occurrences(with_dupes, m, 9));

    /* is_sorted */
    printf("\n--- Sorted check ---\n");
    printf("  Unsorted is sorted: %s\n", is_sorted(unsorted, n) ? "Yes" : "No");
    printf("  Sorted is sorted:   %s\n", is_sorted(sorted, n)   ? "Yes" : "No");

    return 0;
}
gcc search.c -o search -Wall
./search

Output:

=== ARRAY SEARCH FUNCTIONS ===

Unsorted: [64, 34, 25, 12, 22, 11, 90]
Sorted:   [11, 12, 22, 25, 34, 64, 90]

--- Linear search ---
  25 found at index 2
  64 found at index 0
  99 not found

--- Binary search (sorted array) ---
  25 found at index 3
  64 found at index 5
  99 not found

--- Min and Max ---
  Minimum: 11 at index 5
  Maximum: 90 at index 6
  Range:   11 to 90

--- Count occurrences ---
  Array: [3, 7, 3, 2, 7, 7, 1, 3]
  3 appears 3 times
  7 appears 3 times
  9 appears 0 times

--- Sorted check ---
  Unsorted is sorted: No
  Sorted is sorted:   Yes

find_minimum and find_maximum each return one value (the min/max) through return and a second value (the index) through a pointer parameter — the classic pattern for multiple outputs. find_range goes further, returning both min and max through pointers with void return. const int *arr in every read-only function is good practice — the compiler enforces that these functions cannot accidentally modify the array.

Functions in C practice — Program 2: Array transformations by reference

This program implements transformations that modify the array in place. Since arrays are always passed by reference automatically, these functions work on the original data directly.

touch transform.c
gedit transform.c &
#include <stdio.h>

/* Prototypes */
void swap(int *a, int *b);
void reverse(int *arr, int n);
void rotate_left(int *arr, int n, int k);
void rotate_right(int *arr, int n, int k);
void sort_bubble(int *arr, int n);
void sort_insertion(int *arr, int n);
void scale(int *arr, int n, int factor);
void normalise(double *arr, int n);
void print_array(const int *arr, int n);
void print_double_array(const double *arr, int n);

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void reverse(int *arr, int n) {
    int left = 0, right = n - 1;
    while (left < right) {
        swap(&arr[left], &arr[right]);
        left++;
        right--;
    }
}

/* Rotate left by k positions: [1,2,3,4,5] k=2 → [3,4,5,1,2] */
void rotate_left(int *arr, int n, int k) {
    k = k % n;    /* handle k > n */
    if (k == 0) return;

    /* Reverse entire array, then reverse each part */
    reverse(arr, n);
    reverse(arr, n - k);
    reverse(arr + (n - k), k);
}

/* Rotate right by k: [1,2,3,4,5] k=2 → [4,5,1,2,3] */
void rotate_right(int *arr, int n, int k) {
    rotate_left(arr, n, n - k % n);
}

/* Bubble sort — O(n²) */
void sort_bubble(int *arr, int n) {
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {
                swap(&arr[j], &arr[j+1]);
                swapped = 1;
            }
        }
        if (!swapped) break;    /* already sorted — optimisation */
    }
}

/* Insertion sort — O(n²), better for nearly sorted arrays */
void sort_insertion(int *arr, int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1] = key;
    }
}

/* Multiply all elements by factor */
void scale(int *arr, int n, int factor) {
    for (int i = 0; i < n; i++)
        arr[i] *= factor;
}

/* Normalise to [0.0, 1.0] — stores results in double array */
void normalise(double *arr, int n) {
    double min = arr[0], max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
    double range = max - min;
    if (range == 0) {
        for (int i = 0; i < n; i++) arr[i] = 0.0;
        return;
    }
    for (int i = 0; i < n; i++)
        arr[i] = (arr[i] - min) / range;
}

void print_array(const int *arr, int n) {
    printf("[");
    for (int i = 0; i < n; i++) {
        printf("%d", arr[i]);
        if (i < n - 1) printf(", ");
    }
    printf("]");
}

void print_double_array(const double *arr, int n) {
    printf("[");
    for (int i = 0; i < n; i++) {
        printf("%.3f", arr[i]);
        if (i < n - 1) printf(", ");
    }
    printf("]");
}

int main() {
    printf("=== ARRAY TRANSFORMATIONS ===\n\n");

    /* Reverse */
    int arr1[] = {1, 2, 3, 4, 5};
    int n1 = 5;
    printf("--- Reverse ---\n");
    printf("  Before: "); print_array(arr1, n1); printf("\n");
    reverse(arr1, n1);
    printf("  After:  "); print_array(arr1, n1); printf("\n");

    /* Rotate */
    int arr2[] = {1, 2, 3, 4, 5};
    printf("\n--- Rotate ---\n");
    printf("  Original:      "); print_array(arr2, n1); printf("\n");
    rotate_left(arr2, n1, 2);
    printf("  Rotate left 2: "); print_array(arr2, n1); printf("\n");
    rotate_right(arr2, n1, 2);
    printf("  Rotate right 2:"); print_array(arr2, n1); printf("\n");

    /* Sort comparison */
    int arr3[] = {64, 34, 25, 12, 22, 11, 90};
    int arr4[] = {64, 34, 25, 12, 22, 11, 90};
    int n3 = 7;
    printf("\n--- Sorting ---\n");
    printf("  Unsorted: "); print_array(arr3, n3); printf("\n");

    sort_bubble(arr3, n3);
    printf("  Bubble:   "); print_array(arr3, n3); printf("\n");

    sort_insertion(arr4, n3);
    printf("  Insertion:"); print_array(arr4, n3); printf("\n");

    /* Scale */
    int arr5[] = {1, 2, 3, 4, 5};
    printf("\n--- Scale ×3 ---\n");
    printf("  Before: "); print_array(arr5, n1); printf("\n");
    scale(arr5, n1, 3);
    printf("  After:  "); print_array(arr5, n1); printf("\n");

    /* Normalise */
    double arr6[] = {10.0, 20.0, 30.0, 40.0, 50.0};
    printf("\n--- Normalise to [0,1] ---\n");
    printf("  Before: "); print_double_array(arr6, n1); printf("\n");
    normalise(arr6, n1);
    printf("  After:  "); print_double_array(arr6, n1); printf("\n");

    return 0;
}
gcc transform.c -o transform -Wall
./transform

Output:

=== ARRAY TRANSFORMATIONS ===

--- Reverse ---
  Before: [1, 2, 3, 4, 5]
  After:  [5, 4, 3, 2, 1]

--- Rotate ---
  Original:      [1, 2, 3, 4, 5]
  Rotate left 2: [3, 4, 5, 1, 2]
  Rotate right 2:[1, 2, 3, 4, 5]

--- Sorting ---
  Unsorted: [64, 34, 25, 12, 22, 11, 90]
  Bubble:   [11, 12, 22, 25, 34, 64, 90]
  Insertion:[11, 12, 22, 25, 34, 64, 90]

--- Scale ×3 ---
  Before: [1, 2, 3, 4, 5]
  After:  [3, 6, 9, 12, 15]

--- Normalise to [0,1] ---
  Before: [10.000, 20.000, 30.000, 40.000, 50.000]
  After:  [0.000, 0.250, 0.500, 0.750, 1.000]

The rotation algorithm is the most instructive here. rotate_left by k uses three reversal steps — reverse all, reverse first part, reverse second part. The arr + (n - k) expression in reverse(arr + (n - k), k) is pointer arithmetic: it passes a pointer to element n-k of the array, making reverse operate on the tail portion. This is how C functions receive subarrays — by passing a pointer to the start of the portion they should process.

Functions in C practice — Program 3: Statistics calculator

This program implements a complete statistics library as a collection of functions, showing how void functions, return values and pointer parameters all work together in a real computational context.

touch stats.c
gedit stats.c &
#include <stdio.h>

#define MAX_N 100

/* Prototypes */
int  read_array(double *arr, int max);
void print_array(const double *arr, int n);
double sum(const double *arr, int n);
double mean(const double *arr, int n);
double variance(const double *arr, int n);
double std_dev(const double *arr, int n);
double median(double *arr, int n);    /* modifies order — needs copy */
void   mode(const double *arr, int n, double *mode_val, int *frequency);
void   quartiles(double *arr, int n,
                 double *q1, double *q2, double *q3);
void   sort_double(double *arr, int n);
void   histogram(const double *arr, int n, int bins);

/* Read values from user — returns count */
int read_array(double *arr, int max) {
    int n = 0;
    printf("Enter values (type -999 to stop, max %d):\n", max);
    while (n < max) {
        printf("  Value %d: ", n + 1);
        scanf("%lf", &arr[n]);
        if (arr[n] == -999) break;
        n++;
    }
    return n;
}

void print_array(const double *arr, int n) {
    for (int i = 0; i < n; i++)
        printf("%.2f ", arr[i]);
    printf("\n");
}

double sum(const double *arr, int n) {
    double total = 0;
    for (int i = 0; i < n; i++)
        total += arr[i];
    return total;
}

double mean(const double *arr, int n) {
    if (n <= 0) return 0.0;
    return sum(arr, n) / n;
}

double variance(const double *arr, int n) {
    if (n <= 1) return 0.0;
    double m = mean(arr, n);
    double sq_diff_sum = 0;
    for (int i = 0; i < n; i++) {
        double diff = arr[i] - m;
        sq_diff_sum += diff * diff;
    }
    return sq_diff_sum / n;    /* population variance */
}

double std_dev(const double *arr, int n) {
    double v = variance(arr, n);
    /* Manual square root — Newton's method */
    if (v <= 0) return 0.0;
    double x = v;
    for (int i = 0; i < 100; i++)
        x = (x + v / x) / 2.0;
    return x;
}

void sort_double(double *arr, int n) {
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < n - 1 - i; j++)
            if (arr[j] > arr[j+1]) {
                double tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
}

double median(double *arr, int n) {
    /* Works on a copy — caller should pass a duplicate */
    sort_double(arr, n);
    if (n % 2 == 1)
        return arr[n / 2];
    return (arr[n/2 - 1] + arr[n/2]) / 2.0;
}

void mode(const double *arr, int n, double *mode_val, int *frequency) {
    *mode_val  = arr[0];
    *frequency = 0;

    for (int i = 0; i < n; i++) {
        int count = 0;
        for (int j = 0; j < n; j++)
            if (arr[j] == arr[i])
                count++;
        if (count > *frequency) {
            *frequency = count;
            *mode_val  = arr[i];
        }
    }
}

void quartiles(double *arr, int n,
               double *q1, double *q2, double *q3) {
    sort_double(arr, n);
    int half = n / 2;

    /* Q2 = median of whole array */
    if (n % 2 == 1)
        *q2 = arr[n / 2];
    else
        *q2 = (arr[n/2 - 1] + arr[n/2]) / 2.0;

    /* Q1 = median of lower half */
    if (half % 2 == 1)
        *q1 = arr[half / 2];
    else
        *q1 = (arr[half/2 - 1] + arr[half/2]) / 2.0;

    /* Q3 = median of upper half */
    int upper_start = (n % 2 == 1) ? half + 1 : half;
    int upper_n = n - upper_start;
    if (upper_n % 2 == 1)
        *q3 = arr[upper_start + upper_n/2];
    else
        *q3 = (arr[upper_start + upper_n/2 - 1] +
               arr[upper_start + upper_n/2]) / 2.0;
}

void histogram(const double *arr, int n, int bins) {
    double min = arr[0], max = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
    double width = (max - min) / bins;
    if (width == 0) width = 1;

    printf("\nHistogram (%d bins):\n", bins);
    for (int b = 0; b < bins; b++) {
        double lo = min + b * width;
        double hi = lo + width;
        int count = 0;
        for (int i = 0; i < n; i++)
            if (arr[i] >= lo && (arr[i] < hi || b == bins - 1))
                count++;
        printf("  [%5.1f - %5.1f]: ", lo, hi);
        for (int c = 0; c < count; c++) printf("█");
        printf(" (%d)\n", count);
    }
}

int main() {
    double data[MAX_N];
    double data_copy[MAX_N];    /* for operations that modify order */
    int n;

    printf("=== STATISTICS CALCULATOR ===\n\n");

    /* Option: hardcode data for demo */
    double demo[] = {7.5, 3.0, 8.5, 5.0, 9.5, 4.5, 6.0, 7.5, 8.0, 5.5};
    n = 10;
    for (int i = 0; i < n; i++) data[i] = demo[i];

    printf("Data: ");
    print_array(data, n);

    /* Copy for operations that sort in place */
    for (int i = 0; i < n; i++) data_copy[i] = data[i];

    printf("\n--- Descriptive Statistics ---\n");
    printf("N:          %d\n", n);
    printf("Sum:        %.2f\n", sum(data, n));
    printf("Mean:       %.4f\n", mean(data, n));
    printf("Variance:   %.4f\n", variance(data, n));
    printf("Std Dev:    %.4f\n", std_dev(data, n));
    printf("Median:     %.2f\n", median(data_copy, n));

    /* Restore copy for quartiles */
    for (int i = 0; i < n; i++) data_copy[i] = data[i];
    double q1, q2, q3;
    quartiles(data_copy, n, &q1, &q2, &q3);
    printf("Q1:         %.2f\n", q1);
    printf("Q2 (median):%.2f\n", q2);
    printf("Q3:         %.2f\n", q3);
    printf("IQR:        %.2f\n", q3 - q1);

    double mode_val;
    int freq;
    mode(data, n, &mode_val, &freq);
    printf("Mode:       %.1f (appears %d times)\n", mode_val, freq);

    histogram(data, n, 4);

    return 0;
}
gcc stats.c -o stats -Wall
./stats

Output:

=== STATISTICS CALCULATOR ===

Data: 7.50 3.00 8.50 5.00 9.50 4.50 6.00 7.50 8.00 5.50

--- Descriptive Statistics ---
N:          10
Sum:        65.50
Mean:       6.5500
Variance:   3.4475
Std Dev:    1.8567
Median:     7.00
Q1:         5.00
Q2 (median):7.00
Q3:         8.25
IQR:        3.25
Mode:       7.5 (appears 2 times)

Histogram (4 bins):
  [  3.0 -   4.6]: ██ (2)
  [  4.6 -   6.2]: ███ (3)
  [  6.2 -   7.9]: ███ (3)
  [  7.9 -   9.5]: ██ (2)

The data_copy pattern is the key design decision in this program. median and quartiles need to sort the data — but sorting would destroy the original order for subsequent operations. So we copy the array before passing it to any function that modifies order. The rule: if a function’s signature takes double *arr (non-const), it may modify the array — make a copy first if you need the original later. If it takes const double *arr, the original is safe.

Visualise with Python Tutor

Select C from the dropdown and paste in pythontutor.com:

#include <stdio.h>

double mean(const double *arr, int n) {
    double sum = 0;
    for (int i = 0; i < n; i++)
        sum += arr[i];
    return sum / n;
}

void scale(double *arr, int n, double factor) {
    for (int i = 0; i < n; i++)
        arr[i] *= factor;
}

int main() {
    double data[] = {2.0, 4.0, 6.0, 8.0};
    int n = 4;

    printf("Mean before: %.2f\n", mean(data, n));
    scale(data, n, 2.0);
    printf("Mean after:  %.2f\n", mean(data, n));

    return 0;
}

Step through and observe const at work. When mean(data, n) is called, arr receives the address of data[0] — an arrow pointing into main‘s data array. Inside mean, sum += arr[i] reads through the arrow but never writes. The const modifier enforces this — if you tried arr[i] = 0 inside mean the compiler would refuse. When scale(data, n, 2.0) is called, arr also receives the address of data[0], but now arr[i] *= factor follows the arrow and writes back to main‘s data. After scale returns, data in main has doubled values. The same pointer mechanism, same addresses — just const vs non-const determining whether the function can write through it.

Summary and next step

In this article you practised C functions with three real programs. The search program showed read-only functions with const parameters, multiple return values through pointers, and linear vs binary search. The transformations program showed in-place array modification, pointer arithmetic for subarrays, and rotation using the triple-reversal algorithm. The statistics calculator showed a complete modular library — sum called by mean, called by variance, called by std_dev — and the copy-before-sort pattern for functions that destroy order.

In the next article you’ll find exercises to solve on your own in Fedora.

Similar Posts

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *