Normalize a C string in place with a read and a write pointer, collapsing spaces and counting words: an exam question solved and explained step by step.
Updated on
Exam Code C Programming fundamentals Exam level Tested code

Write a C program that reads one line from standard input and normalizes it in place. The line has at most 200 characters, not counting the newline. In place means you modify the character array itself, without any auxiliary array.
Normalizing means two things. In the result, words (maximal runs of non-space characters) are separated by exactly one space. There are no spaces at the beginning or at the end. Only the space character counts as a separator.
Implement the function int normalize(char *s). It modifies the string s and returns the number of words it contains. Inside the function, square-bracket indexing is forbidden. You must walk the string with two pointers, one for reading and one for writing.
The main program reads the line with fgets and removes the trailing newline if there is one. It then calls normalize and prints two lines. The first is the resulting string in brackets, in the format [text]. The second is the number of words. If the line contains no words, print [] and 0. If the input is completely empty (not even one line), treat it as an empty line.
Sample input
hello world Expected output
[hello world]
2- Correct function signature and use of pointers (no array indexing to walk the string)
- 0.5
- Reader/writer logic: collapses internal runs of spaces and removes leading and trailing spaces
- 1.0
- Null terminator written at the right position and correct word count returned
- 0.5
- Main program: safe line reading, newline removal and exact output format
- 0.5
Hints
Hint 1 · How many pointers do you need, and what does each one do?
You can rewrite the string over itself because the result is never longer than the original. Use a pointer r that reads every character and a pointer w that marks where the next kept character goes. At all times w <= r, so you never overwrite a character you have not read yet.
Hint 2 · How do you decide when to write a separating space?
Do not copy the original spaces. Keep a flag that says whether the previous character belonged to a word. At the first non-space character of a new word, write one space before the word, but only if you have already written something. That is the case when w is no longer at the start of the string. With this rule no space appears at either end.
Hint 3 · What happens at the end of the string?
When the loop ends, the normalized text may be shorter than the original. The array still holds the old characters after the new length. Write '\0' where w points. Check the case of a line made only of spaces in your head: w never moved, so the string must end up empty.
Solution
Explained solution
The solution uses the two-pointer technique. A reader moves across the whole array, and a writer advances only when there is a character to keep. Examiners like this pattern for string questions because it forces you to reason about the null terminator and about pointer arithmetic, not just about loops.
The word count comes for free on the same pass. Every time a new word starts, the counter goes up by one. A new word starts at a non-space character that follows a space or the beginning of the string.
#include <stdio.h>
#include <string.h>
#define MAX 201
/* Normalizes s in place and returns the number of words. */
int normalize(char *s) {
char *r = s; /* read pointer */
char *w = s; /* write pointer */
int inWord = 0;
int words = 0;
while (*r != '\0') {
if (*r != ' ') {
if (!inWord) {
if (w != s) {
*w = ' ';
w++;
}
words++;
inWord = 1;
}
*w = *r;
w++;
} else {
inWord = 0;
}
r++;
}
*w = '\0';
return words;
}
int main(void) {
char line[MAX];
if (fgets(line, sizeof line, stdin) == NULL) {
line[0] = '\0';
}
line[strcspn(line, "\n")] = '\0';
int n = normalize(line);
printf("[%s]\n%d\n", line, n);
return 0;
}In normalize, both r and w start at s. The loop while (*r != '\0') visits the whole original string. Suppose *r is not a space and inWord is 0. Then we are at the first character of a new word. We increment words and set the flag. If w != s, we first write a separating space. The test w != s is what prevents a leading space: as long as nothing has been written, w still points to the start.
Next, the character is copied with *w = *r; w++;. If *r is a space, we only clear the flag and write nothing. A run of spaces therefore disappears, whatever its length. Trailing spaces leave no trace either. The separator is always written before the next word, never after the last one, so no word follows to trigger it.
The key invariant is w <= r. For every character read, at most one character is written. The separator is the only extra write, and it happens only after at least one space was read and discarded. That is why we can overwrite the same array without losing data. After the loop, *w = '\0' terminates the string. Without that line, leftovers of the original text would remain visible.
In main, fgets reads at most MAX - 1 characters into line. Then strcspn(line, "\n") returns the position of the newline, or the length if there is none, so the newline is removed safely in both cases. If fgets returns NULL because the input is completely empty, the code stores an empty string. That covers the empty-input test.
Now the tests. The statement example, hello world , exercises leading, internal and trailing spaces at once and yields two words. The single-word test checks that a clean line comes out unchanged. The spaces-only line and the empty input both print [] and 0. In both, w never advances, so the terminator lands at position 0. The remaining tests mix runs of spaces of different lengths: one has four words and the other has three.
Step-by-step trace · generated by running the code
| Step | r | w | inWord | words |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 0 |
| 2 | 1 | 0 | 0 | 0 |
| 3 | 2 | 1 | 1 | 1 |
| 4 | 3 | 2 | 1 | 1 |
| 5 | 4 | 3 | 1 | 1 |
| 6 | 5 | 4 | 1 | 1 |
| 7 | 6 | 5 | 1 | 1 |
| 8 | 7 | 5 | 0 | 1 |
| 9 | 8 | 5 | 0 | 1 |
| 10 | 9 | 5 | 0 | 1 |
| 11 | 10 | 7 | 1 | 2 |
| 12 | 11 | 8 | 1 | 2 |
| 13 | 12 | 9 | 1 | 2 |
| 14 | 13 | 10 | 1 | 2 |
| 15 | 14 | 11 | 1 | 2 |
| 16 | 15 | 11 | 0 | 2 |
| 17 | 16 | 11 | 0 | 2 |
Test cases
| Case | Input | Expected output | Actual output | Result |
|---|---|---|---|---|
| Example: leading, internal and trailing spaces | hello world | [hello world]
2 | [hello world]
2 | OK |
| Single word, no spaces | one | [one]
1 | [one]
1 | OK |
| Line of spaces only | | []
0 | []
0 | OK |
| Empty input | (empty) | []
0 | []
0 | OK |
| Runs of spaces of different lengths | a b c d | [a b c d]
4 | [a b c d]
4 | OK |
| Three words with leading spaces | three short words | [three short words]
3 | [three short words]
3 | OK |
Actual outputs: code compiled with gcc 14.4.0 (C17) and run in an isolated container on 22 October 2026.
Complexity
Time is O(n), where n is the length of the line. The pointer r visits each character exactly once, and each iteration does a constant amount of work. There are no nested loops and no calls to strlen inside the loop. A strlen call in the loop condition would quietly turn the algorithm quadratic.
Extra memory is O(1): two pointers and two integers, besides the array itself. That is what working in place means, and it is why the statement forbids an auxiliary array. A version that copies into a second buffer would also be O(n) in time, but it would use O(n) extra memory and would not meet the requirement.
Common mistakes
- Forgetting to write
'\0'at the end (*w = '\0'). The array keeps the old characters after the new length. Takehello world, which is 13 characters. The result fills positions 0 to 10, and positions 11 and 12 still hold'l'and'd', so the program printshello worldld. - Writing the separator after each word instead of before the next one. This leaves an extra trailing space, or requires a later patch that usually breaks on the empty line.
- Using a single pointer and shifting the rest of the string with
memmoveorstrcpyevery time a repeated space appears. It works, but it costs O(n²), andstrcpyon overlapping regions is undefined behaviour. - Not removing the
'\n'thatfgetskeeps. The newline is not a space, so it is treated as part of the last word and the closing]ends up on the next line. - Declaring the array with exactly 200 elements, which leaves no room for
'\0'. A related mistake is reading withgetsorscanf("%s"). The first function is unsafe and was removed from the language, andscanf("%s")stops at the first space instead of reading the whole line.
Variants
Count words without modifying the string
If counting is enough, you do not need a writer. Walk the string with a single const char * and increment the counter whenever a non-space character follows a space or the start. It is the same inWord flag without the writes. Declare the parameter as const char *s, so the compiler checks that the function never modifies the string.
Reverse the order of the words
After normalizing, reverse the entire string with two pointers moving towards each other, swapping characters as they go. Then reverse each word on its own. Because the string is already normalized, each word ends at a single space or at the terminator. That makes its endpoints easy to find.
Treat tabs as separators too
Replace the comparison *r != ' ' with *r != ' ' && *r != '\t'. A better option is !isspace((unsigned char)*r) from <ctype.h>. The cast avoids undefined behaviour with negative char values. The separator you write is still a single space.
On the syllabus
Pointers and their relationship with arrays are part of the syllabus of Algoritmos y Programación I (95.11) at the Universidad de Buenos Aires. Its Unit III covers arrays, arrays and pointers, array initialization and pointer operators. This exercise practises exactly that boundary: walking a character array with pointer arithmetic instead of indices.
The programme of CC3301 Programación de Software de Sistemas at the Universidad de Chile includes writing systems software in C. Handling memory and pointers safely, as you do here, is the groundwork for that kind of course.
Article generated with AI.larebelion



Comentarios
Publicar un comentario