C String Algorithms & 6 Production Text Processing Projects
Welcome to Phase 8 (Chapter 20): C String Algorithms & 6 Production Text Processing Projects Masterclass! Real-world software systemsβfrom web server URL routers and compiler lexers to security credential validators and search engine scrapersβrely heavily on robust text processing algorithms. In this comprehensive guide, you will master algorithmic paradigms on character streams, explore two-pointer in-place memory mutations, implement a finite-state machine word tokenizer, build an $O(N)$ ASCII character frequency hash table, construct an enterprise-grade username validator, and build a full-fledged Text Analytics Engine.
String reversal and palindrome checking use the classical Two-Pointer Technique:
π 1. In-Place String Reversal Algorithm:
β’ Pointer left starts at index 0, Pointer right starts at index length - 1.
β’ Swap characters at left and right, then increment left++ and decrement right-- until they meet in the middle!
β’ Time Complexity: $O(N/2) = O(N)$ | Auxiliary Memory: $O(1)$ (No extra buffers required!).
π 2. Palindrome Verification Algorithm:
A string is a palindrome if it reads the exact same forwards and backwards (e.g. "racecar" or "madam"). We compare tolower(str[left]) == tolower(str[right]) progressively inward.
Counting words by simply counting spaces is prone to bugs (e.g. multiple consecutive spaces or leading/trailing spaces inflate counts). The professional way to count words is via a 2-State Finite State Machine:
State 0: [ OUT_WORD ] (Currently scanning whitespace / tabs / newlines)
β
β (Encounters non-space character: word_count++ & Transitions to IN_WORD)
βΌ
State 1: [ IN_WORD ] (Currently scanning letters of a word)
β
β (Encounters space/tab/newline: Transitions back to OUT_WORD)
βΌ
State 0: [ OUT_WORD ]
Complete modular architecture implementing the 6 curriculum projects:
Projects 1, 2 & 3: In-Place Reverse, Palindrome Checker & State Machine Word Counter
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
// --- 1. In-Place String Reversal ---
void reverseString(char str[]) {
int left = 0, right = strlen(str) - 1;
while (left < right) {
char temp = str[left];
str[left] = str[right];
str[right] = temp;
left++;
right--;
}
}
// --- 2. Case-Insensitive Palindrome Checker ---
bool isPalindrome(const char str[]) {
int left = 0, right = strlen(str) - 1;
while (left < right) {
if (tolower((unsigned char)str[left]) != tolower((unsigned char)str[right])) {
return false;
}
left++;
right--;
}
return true;
}
// --- 3. State Machine Word Counter ---
int countWords(const char str[]) {
int count = 0;
bool inWord = false;
for (int i = 0; str[i] != '\0'; i++) {
if (isspace((unsigned char)str[i])) {
inWord = false;
} else if (!inWord) {
inWord = true;
count++;
}
}
return count;
}
int main(void) {
char word[] = "RaceCar";
char sentence[] = " C Programming is super fast! ";
printf("Is '%s' Palindrome? %s\n", word, isPalindrome(word) ? "YES" : "NO");
printf("Word Count in Sentence: %d words\n", countWords(sentence));
reverseString(word);
printf("Reversed Word: %s\n", word);
return 0;
}
Projects 4, 5 & 6: Character Frequency Hash Map, Username Validator & Text Analyzer
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
// --- 4. ASCII Character Frequency Map ---
void printCharFrequency(const char str[]) {
int freq[256] = {0}; // Direct-mapped ASCII hash table
for (int i = 0; str[i] != '\0'; i++) {
freq[(unsigned char)str[i]]++;
}
printf("Character Frequencies:\n");
for (int i = 0; i < 256; i++) {
if (freq[i] > 0 && !isspace(i)) {
printf(" '%c' : %d times\n", i, freq[i]);
}
}
}
// --- 5. Production Username Validator ---
// Rules: 3 to 16 chars, alphanumeric or underscore only, must start with letter
bool isValidUsername(const char user[]) {
int len = strlen(user);
if (len < 3 || len > 16) return false;
if (!isalpha((unsigned char)user[0])) return false;
for (int i = 0; i < len; i++) {
char c = user[i];
if (!isalnum((unsigned char)c) && c != '_') return false;
}
return true;
}
int main(void) {
const char username1[] = "dennis_ritchie99";
const char username2[] = "12_invalid";
printf("Validating '%s': %s\n", username1, isValidUsername(username1) ? "VALID" : "INVALID");
printf("Validating '%s': %s\n", username2, isValidUsername(username2) ? "VALID" : "INVALID");
printCharFrequency("banana");
return 0;
}
Q1: Why is an ASCII frequency map array size 256 instead of 26?
Standard extended ASCII contains 256 possible byte values (0 to 255). An array of 256 integers allows direct $O(1)$ indexing for all characters (uppercase, lowercase, numbers, and symbols) without complex conditional branching.
Q2: How do we avoid off-by-one errors when reversing strings?
Always initialize the right pointer to strlen(str) - 1, NOT strlen(str). Swapping str[0] with str[strlen(str)] would swap the null terminator into index 0, truncating the string to an empty string!
Run this text vowel and consonant counter in our live GCC compiler:
#include <stdio.h>
#include <ctype.h>
int main(void) {
char text[] = "Dennis Ritchie invented C";
int vowels = 0, consonants = 0;
for (int i = 0; text[i] != '\0'; i++) {
char c = tolower((unsigned char)text[i]);
if (isalpha(c)) {
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') vowels++;
else consonants++;
}
}
printf("Vowels: %d | Consonants: %d\n", vowels, consonants);
return 0;
}