Search code examples
cfile-readrecord-count

How to display the count of records in a file?


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,

  1. Create the appointment.dat file
  2. Get 5 patient details through keyboard input and write the data into the appointment.dat file under the given sample format.
  Dave                C
  Ryan                T
  Mary                S
  George              C
  Julian              S
  1. Read the 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?


Solution

  • 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?