While you can break your tests up into multiple functions, since the number of cases you are handling is limited, simply including the tests directly in a single "state" loop that keeps track of the number of newlines and whether there have been any modifications to the input is an equally readable way to go. (good catch @iBug on the XY-Problem).
There are a number of ways to approach a character classification problem, which is essentially what you have. (meaning you read a character, classify whether if fits some predefined test, and then take some action depending on how it is classified). One efficient way to handle it is with a loop that keeps track of the state of things (e.g. "How many newlines have been sequentially read?", or whether I'm inside a word, reading whitespace, etc..) by setting, or resetting or incrementing a few variables that track whatever condition (or state) you are interested in that is based on something other than the current character.
You simply figure out what you need to follow (here, the number of sequential newlines, and whether you have modified the output in any way). Easy enough, just keep a count of the sequential newlines read in an integer like nl
and keep an integer flag you set if you make any changes, call it modified
or something logical. When you read anything other than a '\n'
reset your newline count, e.g. nl = 0;
.
Then it is just a matter of reading each character of input until you encounter EOF
and conducting a number of tests to determine what character you read (either using if, else if, else
or using a switch
statement). Then for each different test, take the appropriate action.
For example, you could satisfy your criteria with something simple like:
#include <stdio.h>
int main (void) {
int c, modified = 0, nl = 0;
while ((c = getchar()) != EOF) { /* loop over each char */
if (c == '\n') { /* am I a '\n'? */
if (nl < 2) /* have I ouput less than 2? */
putchar ('\n'); /* output the newline */
else
modified = 1; /* set modified flag */
nl++; /* update the number seen */
continue; /* get next char */
}
else if ('A' <= c && c <= 'Z') { /* am I uppercase? */
putchar (c + 'a' - 'A'); /* convert to lowercase */
modified = 1; /* set modified flag */
}
else if ('a' <= c && c <= 'z') { /* am I lowercase? */
putchar (c + 'A' - 'a'); /* convert to uppercase */
modified = 1; /* set modified flag */
}
else if (c < '0' || '9' < c) { /* am I not a digit? */
if (c == ' ') { /* am I a space ? */
putchar (c); /* output extra space */
modified = 1; /* set modified flag */
}
putchar (c); /* output unmodified char */
}
nl = 0; /* reset newlines seen to zero */
}
return modified ? 0 : 1; /* 0 - modified, 1 - stdout == stdin */
}
note: try and avoid using magic numbers in your code. If you need the ASCII value of 'A'
, then use 'A'
, not 65
. Reading thought code that has magic numbers scattered about (e.g. 65
, 32
, 10
is not nearly as informative as 'A'
, ' '
and '\n'
).
Note also, you can use the macros is ctype.h
(e.g. isupper()
, islower()
, isdigit()
, etc..) to easily test what the current character is in a very readable manner. There is also learning value in doing it manually to gain an understanding of what those convenient functions in ctype.h
are actually doing and practicing setting up your conditional tests properly.
Example Input File
Now just create a test case to exercise your character classifications and make sure you loop is working the way it should. There is nothing special required. Rather than trying to construct a myriad of assert()
statements, just create an input file that has a character that fits each case:
$ cat dat/stateloop.txt
This Is a Line followed by THREE-newlines
and with SEVEN single-spaced asterisk * * * * * * *
aND a lINE fOLLOWED bY five nEWLINES ('\N')
And A Few Numbers zERO-tO-nINE (123456789)
1
a b c d e f g - A B C D E F G
Done2Day
Example Use/Output
Then carefully check the output:
$ ./bin/stateloop <dat/stateloop.txt
tHIS iS A lINE FOLLOWED BY three-NEWLINES
AND WITH seven SINGLE-SPACED ASTERISK * * * * * * *
And A Line Followed By FIVE Newlines ('\n')
aND a fEW nUMBERS Zero-To-Nine ()
A B C D E F G - a b c d e f g
dONEdAY
Check Return Value
and validate the return
is correct.
$ echo $?
0
Try A (no-change) Case
Do the same thing for the non-modified case:
$ printf "~@##$@#$%%#*[]{};:^^&&*)\n" | ./bin/stateloop
~@###$%#*[]{};:^^&&*)
Check Appropriate Return
$ echo $?
1
As noted earlier, there are many ways to approach this type of problem. Just find something that is logical, robust, readable and understandable. Good luck with your coding.