Two Ways to Count Vowels in C: Switch vs. Nested Loops
When learning C, strings present an exciting challenge because they aren’t a built-in high-level data type—they are simply arrays of characters ending with a null terminator (\0). To explore how we can manipulate strings and manage control flow, we can look at two completely different strategies to solve a classic problem: Counting vowels in a string.
Method 1: The Switch Statement Approach
This method uses an optimized switch statement that evaluates each character of the string directly against an explicit list of matches.
#include <stdio.h>
int count_vowels_switch(const char *str);
int main(void) {
char test1[] = "Wojtek";
char test2[] = "Tomaszewski";
char test3[] = "";
char test4[] = "Alice";
printf("--- Testing Version 1: Switch Statement ---\n");
printf("Number of vowels in %s is %d\n", test1, count_vowels_switch(test1));
printf("Number of vowels in %s is %d\n", test2, count_vowels_switch(test2));
printf("Number of vowels in %s is %d\n", test3, count_vowels_switch(test3));
printf("Number of vowels in %s is %d\n", test4, count_vowels_switch(test4));
return 0;
}
int count_vowels_switch(const char *str) {
int counter = 0;
while (*str != '\0') {
switch (*str) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
case 'y':
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
case 'Y':
counter++;
break;
}
str++; /* Advance pointer to the next character */
}
return counter;
}
Code Architecture & Design Notes (Switch Version)
- Pointer Traversal (
while (*str != '\0')): Instead of keeping track of an integer index variable (likestr[i]), this function uses direct pointer arithmetic. Becausestrholds the memory address of the string’s start, dereferencing it with*strlets us see the single character at that exact address. Executingstr++advances the memory pointer forward by 1 byte to point to the next character until it reaches the null terminator. - Switch Case Fall-Through: We avoid repeating the
counter++; break;statement twelve times by leveraging a structural property ofswitchblocks called fall-through. In C, if acasematches but lacks abreak;statement, execution cascades straight down into the next row. Grouping all twelve uppercase and lowercase vowel cases sequentially on top of a single block ensures they all route to the same increment code.
Method 2: The Nested Loops Approach
This method takes a dynamic approach by cross-referencing each character of the input text against a standalone string literal containing all targets.
#include <stdio.h>
int count_vowels_nested(const char *str);
int main(void) {
char test1[] = "Wojtek";
char test2[] = "Tomaszewski";
char test3[] = "";
char test4[] = "Alice";
printf("--- Testing Version 2: Nested Loops ---\n");
printf("Number of vowels in %s is %d\n", test1, count_vowels_nested(test1));
printf("Number of vowels in %s is %d\n", test2, count_vowels_nested(test2));
printf("Number of vowels in %s is %d\n", test3, count_vowels_nested(test3));
printf("Number of vowels in %s is %d\n", test4, count_vowels_nested(test4));
return 0;
}
int count_vowels_nested(const char *str) {
const char *vowels = "aeiouyAEIOUY";
int counter = 0;
while (*str != '\0') {
/* Inner loop checks the current character against every vowel in our reference string */
for (int j = 0; vowels[j] != '\0'; j++) {
if (*str == vowels[j]) {
counter++;
break; /* Stop checking other vowels once a match is found */
}
}
str++; /* Advance pointer to the next character */
}
return counter;
}
Code Architecture & Design Notes (Nested Loops Version)
- Linear Search Matrix: The outer loop steps through each letter of the input string one by one. For every single letter, the inner
forloop kicks off, starting from indexj = 0and stepping through the"aeiouyAEIOUY"array until it matches or reaches the string’s end. - Short-Circuiting with
break: When a match is found (*str == vowels[j]), we increment our counter and immediately invokebreak;. This optimization terminates the inner loop early. Because a character can never be more than one vowel at the same time, continuing to check the remaining entries in the lookup string would be wasted CPU cycles.
Algorithmic Complexity Analysis
Understanding how code behaves as input scales is vital for writing performant systems software. Here is the breakdown for both approaches:
Time Complexity
-
Method 1 (Switch Statement): $O(N)$
-
How it works: Let $N$ represent the number of characters in the input string. A
switchstatement is highly optimized by C compilers (often compiling down to a low-level “jump table”). This allows the system to determine whether a character matches a vowel in essentially constant time ($O(1)$) per letter. As a result, execution time scales purely linearly with the length of the string. -
Method 2 (Nested Loops): $O(N \times M) \rightarrow O(N)$
-
How it works: Let $N$ be the input string length and $M$ be the fixed size of our lookup target array (12 vowels).
-
Worst-Case Scenario: If a character is a consonant (like
'w'), the inner loop searches all 12 entries and hits the end without breaking. For a string containing only consonants, the program executes exactly $12 \times N$ operations. Because 12 is a constant factor, it simplifies to $O(N)$ asymptotically, but it performs up to 12 times more work per character than theswitchvariant. -
Optimization Note: By placing highly common vowels (like
'a'and'e') at the very beginning of the reference string, we reduce the average number of steps the inner loop must perform before striking a match and breaking early.
Space (Memory) Complexity
- Both Versions: $O(1)$ (Constant Space)
- Why: Regardless of whether you pass a string with 5 characters or 5 million characters, neither function allocates additional heap memory. They only keep track of a few isolated primitive variables on the stack frame (
counter, the pointer tracker, and loop indexj). The memory footprint remains perfectly flat and fixed throughout execution.