The scenario goes like this,
A medical center needs to store appointment details in a text file called appointment.dat.
It includes the Patient name and the Appointment type. The appointment types can either be 'Consulting', 'Scanning' or 'Testing'. Only the first letter is stored in the file.
The requirement is to,
appointment.dat
fileappointment.dat
file under the given sample format. Dave C
Ryan T
Mary S
George C
Julian S
appointment.dat
file and calculate and display the Number of Patients under the given format. Appointment Type Number of patients
Consulting 2
Scanning 2
Testing 1
Here's the code I tried,
#include <stdio.h>
int main()
{
int cCount, sCount, tCount;
char name, chan, C, S, T, c, s, t;
chan = " ";
cCount = sCount = tCount = 0;
FILE *cPtr;
cPtr = fopen ("appointment.dat", "r");
while (!feof(cPtr))
{
fscan (cPtr, "%s", &chan);
if (chan == C)
cCount++;
else if (chan == S)
sCount++;
else if (chan == T)
tCount++;
}
printf ("Appointment Type Number of patients\n");
printf ("Consulting %d \n");
printf ("Scanning %d \n");
printf ("Testing %d \n");
return 0;
}
I'm having trouble getting the displaying part right. The Number of Patients displays some addresses instead of the patient count.
How do I modify the code to get the patient count right?
You have fallen into one of the first pitfalls most new C-programmers fall into. Why is while ( !feof (file) ) always wrong? On your call to fscan (cPtr, "%s", &chan);
(which should be fscanf
), for the last line of input, the read succeeds and EOF
is not set. You test while (!feof(cPtr))
-- and it's NOT. You loop again and reach fscan (cPtr, "%s", &chan);
which now fails due to an input-failure and EOF
is returned -- but you blindly proceed to check if (chan)
which may fail at this point or may appear to work correctly (adding an additional erroneous count to the variable corresponding to whatever the last value of chan
was). 1
You further invoke Undefined Behavior in your use of printf
by failing to provide any argument for the "%d"
conversion specifier contained in the format string, e.g.
printf ("Consulting %d \n");
C11 Standard - 7.21.6.1 The fprintf function(p2) (this explains your "The Number of Patients displays some addresses instead of the patient count.")
When you are reading a line-of-input at a time, use a line-oriented input function like fgets()
or POSIX getline()
to read a complete line into a sufficiently sized array, and then use sscanf()
to separate the array into the needed values, e.g.
#define MAXC 128 /* if you need a constant, #define one (or more) */
...
char buf[MAXC], name[MAXC], type;
...
while (fgets (buf, MAXC, fp)) { /* read each line into buf */
if (sscanf (buf, "%s %c", name, &type) == 2) { /* split buf into name, type */
You use the return of the read function itself to control the continuation of the read-loop. You cannot use any user-input function correctly without Checking the return
.
Now you can check the type
. A simple way is to use a switch()
statement -- though there is nothing wrong with a sequence of if()
statements. You can use a switch()
similar to:
switch (type) { /* switch on type */
case 'C': C++; break;
case 'S': S++; break;
case 'T': T++; break;
default:
fprintf (stderr, "error: invalid type '%c'\n", type);
break;
}
Putting it altogether, and allowing the filename to be passed as the first argument to the program (or read from stdin
by default if no filename is given), you could do:
#include <stdio.h>
#define MAXC 128 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
char buf[MAXC], name[MAXC], type;
size_t C = 0, S = 0, T = 0;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line into buf */
if (sscanf (buf, "%s %c", name, &type) == 2) { /* split buf into name, type */
switch (type) { /* switch on type */
case 'C': C++; break;
case 'S': S++; break;
case 'T': T++; break;
default:
fprintf (stderr, "error: invalid type '%c'\n", type);
break;
}
}
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
printf ("Appointment Type Number of patients\n" /* output results */
"Consulting %9zu\n"
"Scanning %9zu\n"
"Testing %9zu\n", C, S, T);
}
(note: you only need one printf
statement for each contiguous output. The C compiler will concatenate all adjacent whitespace separated strings, e.g. "..."
"..."
into a single string)
Example Use/Output
Simply providing your input to the program on stdin
using a bash heredoc, you would get:
$ cat << eof | ./bin/appointments
> Dave C
> Ryan T
> Mary S
> George C
> Julian S
> eof
Appointment Type Number of patients
Consulting 2
Scanning 2
Testing 1
Reading From A File
With your data in the file dat/appointment_types.txt
, e.g.
$ cat dat/appointment_types.txt
Dave C
Ryan T
Mary S
George C
Julian S
Your use and output would be:
$ ./bin/appointments dat/appointment_types.txt
Appointment Type Number of patients
Consulting 2
Scanning 2
Testing 1
Look things over and let me know if you have further questions.
Footnotes:
1. The C-Standard does not define what chan
will hold after an input-failure occurs. C11 Standard - 7.21.6.2 The fscanf function(p9) The behavior is simply undefined because chan
is indeterminate at this point and used while it has an indeterminate value. C11 Standard - J.2 Undefined Behavior "The value of an object with automatic storage duration is used while it is indeterminate (6.2.4, 6.7.9, 6.8)."
and see discussion Undefined, unspecified and implementation-defined behavior and What is indeterminate behavior in C++ ? How is it different from undefined behavior?