Here is a sample program that uses a variety of the important string
commands provided by C. The program solves the following problem:
Problem: You are given a list of names that are separated by the
word "end". For example:
Yifan Tang end
Brad Vander Zanden end
Nels Blank
Vander Zanden end
Your program should print:
- the longest name (i.e., the name with the longest length)
- the first name in alphabetical order
In the above input, "Nels Blake Vander Zanden" is the longest name
and "Brad Vander Zanden" is the first name in alphabetical order.
Note that names are typically comprised of multiple words separated by
blank spaces, so you will need to use strcat to concatenate the
words together to form a name. You will need to use the strlen
function to determine the length of the longest string, strcmp
to find the first name in alphabetical order and to determine when
you have encountered an "end" token, and strcpy to save a copy
of the longest length word and a copy of the first word in alphabetical order.
The Program
I have included some commentary at the end of this program.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_SINGLE_NAME 20
#define MAX_NAME 100
main() {
char word[MAX_SINGLE_NAME];
char name[MAX_NAME] = "";
char longest[MAX_NAME] = "";
char smallest[MAX_NAME] = "";
/* read an initial word into name. I do not want to wait to initialize
name until I am in the loop, because the first strcat
statement will add a blank space to the front of the name. */
scanf("%s", name);
/* read words until EOF */
while (scanf("%s", word) != EOF) {
if (strcmp(word, "end") == 0) {
if (strlen(name) > strlen(longest)) {
strcpy(longest, name);
}
/* determine if the current name is the new smallest name */
if (strcmp(smallest, "") == 0) {
strcpy(smallest, name);
}
else if (strcmp(name, smallest) < 0) {
strcpy(smallest, name);
}
/* I used to copy an empty string to name to re-initialize it,
but I discovered that this was causing me to have a space
before the start of the first name, because strcat starts
by appending a space to the name. I thus changed the code
so that it now reads the next word directly into name. This
assumes that there will not be an empty name (i.e., there will
not be two consecutive "end" tokens in the input) */
scanf("%s", name); /* start the next name */
}
/* concatenate words into a bigger name until I see an "end" token */
else {
strcat(name, " ");
strcat(name, word);
}
}
/* print longest name */
printf("longest = %s\n", longest);
/* print first name in alphabetical order */
printf("smallest = %s\n", smallest);
return(0);
}
Commentary
Here are some issues that I encountered or thought about as I developed
the program:
- I initially developed the program without worrying about boundary conditions.
Boundary conditions are special cases that occur at the start or
end of the program (e.g., how do you initialize a variable), or
that involve
special cases in the input (e.g., how do you handle an empty file).
By initially ignoring boundary conditions, I was able
to focus on the main logic of the program, without
worrying about the annoying, but important details of boundary
conditions.
- Once I had written the initial code, I knew that I had not initialized
many of the important variables, such as longest, smallest,
and name. It is important to get variable initialization
correct and as you will see, I did not get it entirely correct on
my first try. I took a different approach to initializing each of these
three variables:
- longest: I knew that every string would be longer than
0 characters, so I initialized longest to be the empty
string. I initially used a special test to determine
whether or not longest had been initialized. However,
after I wrote the program I realized that I
did not need a special test to see whether
or not longest had been initialized, because I knew that
the first name would be longer than the empty string stored
in longest, and hence longest would be immediately
set to the first name I encountered. Hence I cleaned up the
code by eliminating the test of whether or not longest
had been initialized.
- smallest: Unlike longest, it is hard to find
a reasonable string that will be always be last in alphabetical
order. I therefore decided to leave a
"marker" in smallest that indicated that it was not
yet initialized. A good marker is the empty string, since a
name cannot be the empty string. In my code, I checked to see
whether or not smallest contained the empty string,
before checking whether or not my current name was before
smallest in alphabetical order. This check ensures that
the very first name I encounter will be used to initialize
smallest.
- name: I initially thought I could initialize name
to be the empty string, and then simply concatenate words to
name. However, when I tested the program, I discovered that
all my names started with a blank space. For example, instead
of "Brad Vander Zanden" I was getting " Brad Vander Zanden".
The problem turned out to be that the strcat code for appending
words to names first appends a space. So if I initialize
name to the empty string, then what happens is that before
the first word is added, a space is appended to the empty string,
thus creating a name with a leading space. This is an example
of a "boundary" condition causing a problem. In this case the
boundary condition is what happens with the first word added
to the name. I realized that I could overcome this problem by
simply reading a word from the input stream into name whenever
I needed to start a new name. This works only if I am sure
that a name cannot be empty (i.e., there cannot be
two consecutive "end" words). If I need to check for empty
names, my code would be a bit more complicated. My trick of
reading a word into name works even when I've reached
EOF. The read will fail and hence will leave the old name
in name. The program will then continue back to the top of
the while loop. The while loop will try another scanf, and the
scanf will again fail, thus causing the loop to exit. I will
never use name again, and hence it does not matter that
name has an invalid value.