Skip to content

Solutions: Lecture 4 In-Class Exercises

Instructor key for exercises.md. Keep separate from the student handout.


Part A - Integer arrays

Exercise A1 - Largest and smallest

#include <stdio.h>

int main(void) {
    int a[] = {42, 17, 99, 3, 56, 8};
    int n = 6;                 /* six values, so n is 6 */

    int min = a[0];
    int max = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] < min) {
            min = a[i];
        }
        if (a[i] > max) {
            max = a[i];
        }
    }

    printf("largest:  %d\n", max);
    printf("smallest: %d\n", min);
    return 0;
}
largest:  99
smallest: 3
  • Seed from a[0], not 0. If you start max = 0 and every value is negative, you wrongly report 0 as the largest. Seeding from an actual element avoids assuming anything about the data.
  • Loop from i = 1 since element 0 is already accounted for.
  • Stretch (index of the max): keep an int max_idx = 0; and set max_idx = i; in the same branch that updates max.

Exercise A2 - A sum function (and an average)

#include <stdio.h>

int sum(int a[], int n) {
    int total = 0;
    for (int i = 0; i < n; i++) {
        total += a[i];
    }
    return total;
}

double average(int a[], int n) {
    return (double) sum(a, n) / n;     /* cast BEFORE dividing */
}

int main(void) {
    int a[] = {42, 17, 99, 3, 56, 8};
    int n = 6;                 /* six values, so n is 6 */

    printf("sum:     %d\n", sum(a, n));
    printf("average: %.2f\n", average(a, n));
    return 0;
}
sum:     225
average: 37.50
  • The cast must happen before the division. sum(a, n) / n is integer division (it would print 37.00); (double) sum(a, n) / n does real division.
  • average reuses sum - no second loop needed.
  • n is passed explicitly because inside these functions a is just a reference to where the array starts - the count is not carried with it, so the caller has to supply it.

Part B - Strings

Exercise B1 - Count uppercase and lowercase

#include <stdio.h>

int main(void) {
    char word[256];

    printf("Enter a word: ");
    scanf("%s", word);

    int upper = 0, lower = 0;
    for (int i = 0; word[i] != '\0'; i++) {
        char c = word[i];
        if (c >= 'A' && c <= 'Z') {
            upper++;
        } else if (c >= 'a' && c <= 'z') {
            lower++;
        }
    }

    printf("uppercase: %d\n", upper);
    printf("lowercase: %d\n", lower);
    return 0;
}
Enter a word: HelloWorld
uppercase: 2
lowercase: 8
  • The loop condition word[i] != '\0' is the standard "walk to the terminator." scanf("%s", word) reads one whitespace-delimited word and writes the '\0' for you, so there is no stray newline to worry about.
  • Stretch (digits and other): add else if (c >= '0' && c <= '9') digits++; and an else for everything else, then check upper + lower + digits + other == length.

Exercise B2 - Redact the letters (modify in place)

#include <stdio.h>

int main(void) {
    char word[256];

    printf("Enter a word: ");
    scanf("%s", word);

    for (int i = 0; word[i] != '\0'; i++) {
        char c = word[i];
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
            word[i] = '#';
        }
    }

    printf("redacted: %s\n", word);
    return 0;
}
Enter a word: abc123XYZ!
redacted: ###123###!
  • The whole exercise is the assignment word[i] = '#'; - you are changing the array in place as you walk it, exactly the move HW1's case conversion and cipher need. Here the per-character decision is deliberately trivial so the mechanics are the only new thing.
  • The || joins the two case ranges so both upper and lower letters are caught.
  • Non-letters fall through with no else, so digits and punctuation are left exactly as typed.

Stretch

1. Letter-frequency histogram

#include <stdio.h>

int main(void) {
    char word[256];

    printf("Enter a word: ");
    scanf("%s", word);

    int counts[26] = {0};
    for (int i = 0; word[i] != '\0'; i++) {
        char c = word[i];
        if (c >= 'A' && c <= 'Z') {
            c = c - 'A' + 'a';        /* fold case so 'A' and 'a' share a bin */
        }
        if (c >= 'a' && c <= 'z') {
            counts[c - 'a']++;        /* the letter itself is the index */
        }
    }

    for (int k = 0; k < 26; k++) {
        if (counts[k] > 0) {
            printf("%c: %d\n", 'a' + k, counts[k]);
        }
    }
    return 0;
}
  • counts[c - 'a'] is the key idea: c - 'a' turns 'a'..'z' into 0..25, a valid index. That same letter-to-0..25 mapping is what the HW1 cipher does before it shifts and wraps with % 26.
  • Folding uppercase to lowercase first means 'A' and 'a' land in the same bin.
  • Printing back uses the inverse map, 'a' + k, to recover the letter from its index.

2. Find a character

int index_of(char s[], char target) {
    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] == target) {
            return i;          /* first match wins */
        }
    }
    return -1;                 /* walked the whole string, never found it */
}
  • A plain walk that returns early on the first hit; falling off the loop means it was not there, so we return the sentinel -1. This early-return-or-sentinel shape shows up in lots of string code.

3. Most common letter

/* assumes counts[26] has already been filled as in stretch 1 */
int best = 0;
for (int k = 1; k < 26; k++) {
    if (counts[k] > counts[best]) {
        best = k;
    }
}
printf("most common: %c (%d)\n", 'a' + best, counts[best]);
  • This is exercise A1's maximum scan applied to the histogram: track the index of the largest count seen so far, starting from best = 0 and checking from k = 1. Combining the string walk (to fill counts) with the array scan (to find the max) is the kind of two-step build HW1 problems ask for.