0

I am simply trying to read all the information from a file and print it back to the screen, but it only prints one row of info.

Also, if you notice from the 3rd row in the txt file the last column has no ID info, and before when I had my code formatted a different way it was reading up until that 3rd row and then became an infinite loop because the ID was non-existent.

However, as it is now it only reads the first row of information from the text file and then comes to a stop and I can't figure out why.

NOTE: I have certain constraints, and I am only able to be used fscanf() to read from the file.

Code:

/*
Room#   Equipment      Bed#     Personnel    PatientID (these headings are not supposed to be in the text file)

230    respirator       2       none         1000212,1000217
231    none             4       nurse        1000214
232    none             2       doctor  
233    none             1       nurse        1000219

*/


#include <conio.h>
#include <stdio.h>

typedef struct Rooms
{
    int roomNum;
    char respirator[11];
    int bedNum;
    char personnel[7];
    int currPatients[5];
} rooms;

void aiAssign();

int main(void)
{
    aiAssign();

    getch();
    return 0;
}

void aiAssign()
{
    rooms build1;
    FILE* rPtr = fopen("rooms.txt", "r");

    while(fscanf(rPtr,"%d\t%s\t%d\t%s\t", &build1.roomNum, build1.respirator, &build1.bedNum, build1.personnel) == 4)
    {
        int i=0;
        while(fscanf(rPtr,"%d%*c ", &build1.currPatients[i++]) == 1);

        printf("%d %s %d %s", build1.roomNum, build1.respirator, build1.bedNum, build1.personnel);
        for (i=0;i<2;i++)
        {
            if (build1.currPatients[i] >= 1000000)
            {
                printf("\t%d", build1.currPatients[i]);
            }
        }
        printf("\n");
    }

    fclose(rPtr);
}

2 Answers2

0

I think you are over complicating it, if your only purpose is to read the file line by line and then print it to the screen, you could use the simpler functions open, read and write. Check them. Define a buffer and use it to read, then use stdout file descriptor to write the data you read in the buffer . Keep the looping till the number of bytes read returned by the read function is less than the buffer size. The file descriptor for the stdout is 1. Here is a link that explains how these functions work https://www.google.es/amp/s/www.geeksforgeeks.org/input-output-system-calls-c-create-open-close-read-write/amp/

If you need to convert the raw text file to those data structures then you a more intelligent processing.

The functions fscanf and printf are higher level built on top of these ones I suggest you. They receive formats strings which uses as guidelines to automate processing like the integer conversion from ascii when you specify %d . This was an example .

0

Reading with fscanf() is doable (if you are very careful) but is NOT the correct way to approach this problem. Why?

When reading with fscanf() all conversion specifiers except %c, %[..] and %n discard leading whitespace. The '\n' marking the end of the line is whitespace. So reading with fscanf() you have no way to prevent reading past the '\n' and extracting data from the following line if you attempt to read with more conversions specifiers than there is data for (unless you are very careful)

Normally, you would read the entire line-at-a-time with fgets() or POSIX getline(), then you can trivially pass the line to sscanf() to attempt to extract all data using a single format-string. That way the end-of-line is respected with each read and there is no possibility of reading past the end of line.

That said, it is doable. Before looking at how, let's look at how you will store your data. Unless you simply want to separate and print the data, you will need an array of rooms to store each line (record) worth of data. You should declare the array in main() and pass it as a parameter to aiAssign(). You can declare the array as follows:

#define MAXROOMS 10     /* if you need a constant, #define one (or more) */
...
int main (int argc, char **argv)
{
    rooms build1[MAXROOMS] = {{ .roomNum = 0 }};    /* array of rooms, init all zero */
    size_t nrooms = 0;                              /* number of rooms */

(note: it is imperative you initialize the array of struct all zero because your struct Rooms does not contain a counter for the number of elements stored in you currPatients array. Failing to initialize, you will have no way to access the stored values without invoking Undefined Behavior)

Generally, you also want to open and validate your file is open for reading in the calling function and pass the open FILE* pointer as a parameter to the function to use. If you can't open and validate the file is open in the caller, there is no reason to make your function call to begin with.

Additionally, do not hardcode filenames in your code. Pass the filename as an argument to your program (that's what int argc, char **argv are for) or prompt the user and have them input the filename. You should not have to recompile your program just to read from a different input file. You can take the filename as the first argument to your program, or read from stdin by default if no arguments are given as follows:

    /* 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;
    }

Your aiAssign() has another fatal flaw. With a return type of void, you have no way of communicating how many rooms of data were successfully read from the input file back to the calling function (main() here). For any function that needs to return a positive value, a count, or a length, the choice of size_t as the return type of size_t makes sense and generally, but not always, offers a greater range than int. For example, the prototype for your function, incorporating all that has been discussed above could be:

/* pass open FILE* to read, and pointer to array of rooms to fill
 * returns number of rooms of data stored in array
 */
size_t aiAssign (FILE* fp, rooms *r);

Using fscanf() To Read The File

With the preliminaries out of the way, let's look at how you can read your input file with fscanf(). For the reasons related to the end-of-line and whitespace discussed above, you cannot use a single format string to read each line entirely. At best you have to break the read of each line worth of data up into two calls to fscanf(), one to read all information through the .personnel member of your struct, and the second call to read the .currPatients[] array.

In between the two calls you must check for EOF (an input failure -- where EOF is reached before the first valid conversion takes place). You must also scan forward in the input stream using fgetc() (or you can use fscanf() with the %c conversion, up to you) to locate either the next non-whitespace character or the '\n' manually. You can only attempt to read data for the .currPatients[] if additional characters exist in the line. Otherwise, since the %d conversion specifier will ignore leading whitespace, it will happily read and discard the '\n' and begin reading the roomNum member from the following line.

Once you have made the determination and either made or skipped the second call to fscanf(), you need a way of determining if you read valid data. Here, given your input file, what you really care about is that at least four successful conversions took place in the first call to fscanf() and from zero to five conversions took place reading the currPatients[] array data. That gives a range of valid returns from four to nine conversions.

(with any input function you do that by checking the return -- if you take nothing else from this answer, understand you cannot use any input function correctly unless you check the return to validate success or failure of the input)

Putting it altogether for your aiAssign() function, you could do something similar to:

/* pass open FILE* to read, and pointer to array of rooms to fill
 * returns number of rooms of data stored in array
 */
size_t aiAssign (FILE* fp, rooms *r)
{
    size_t n = 0;       /* no. of rooms counter */
    
    /* while array not full */
    while (n < MAXROOMS) {
        int haspts = 0,     /* flag indicating if patients present to read */
            rtn1 = 0,       /* return for first fscanf */
            rtn2 = 0;       /* return for second fscanf */
        
        /* read roomNum, respirator, bedNum and personnel */
        rtn1 = fscanf (fp, "%d %10s %d %6[^ \t\n]", &r[n].roomNum, r[n].respirator, 
                    &r[n].bedNum, r[n].personnel);
        if (rtn1 == EOF)    /* if input-failure (no conversions before EOF), break */
            break;
        
        /* loop to find next character or newline */
        for (int c = fgetc(fp); c != EOF; c = fgetc(fp))
            if (!isspace(c)) {      /* if non-whitespace character */
                ungetc (c, fp);     /* put it back in input stream */
                haspts = 1;         /* set has patients flag true */
                break;
            }
            else if (c == '\n')     /* if newline, leave has patients false */
                break;
        
        if (haspts)     /* if patient info to read, attempt to fill array */
            rtn2 = fscanf (fp, "%d,%d,%d,%d,%d", &r[n].currPatients[0],
                        &r[n].currPatients[1], &r[n].currPatients[2],
                        &r[n].currPatients[3], &r[n].currPatients[4]);
        
        /* no patients or first fscanf all conversions took place */
        if ((rtn1 && !rtn2) || (rtn1 == 4)) {
            /* validate at least 4 up to 9 total conversions took place */
            switch (rtn1 + rtn2) {
                case 9:
                case 8:
                case 7:     /* case fall-through intentional */
                case 6:
                case 5:
                case 4:
                    n++;    /* increment counter */
                    break;
            }
        }
        /* remove all characters up to '\n' */
        if (haspts)
            for (int c = fgetc(fp); c != '\n' && c != EOF; c = fgetc(fp)) {}
    }
    
    return n;   /* return number of rooms */
}

(note: the haspts (short for "has patients") flag is used to indicate whether the second call to fscanf() for currPatients data is needed. Note also you cannot read string data with any of the scanf() family of functions without also including the field-width modifier to prevent reading more characters than your array can hold, resulting in a buffer overrun. Without the field-width modifier, reading string data with scanf() is no safer than using gets(), see Why gets() is so dangerous it should never be used!)

A short example using the function to read the data from your file using only fscanf() could be done as:

#include <stdio.h>
#include <ctype.h>

#define MAXROOMS 10     /* if you need a constant, #define one (or more) */

typedef struct Rooms {
    int roomNum;
    char respirator[11];
    int bedNum;
    char personnel[7];
    int currPatients[5];
} rooms;

/* pass open FILE* to read, and pointer to array of rooms to fill
 * returns number of rooms of data stored in array
 */
size_t aiAssign (FILE* fp, rooms *r)
{
    size_t n = 0;       /* no. of rooms counter */
    
    /* while array not full */
    while (n < MAXROOMS) {
        int haspts = 0,     /* flag indicating if patients present to read */
            rtn1 = 0,       /* return for first fscanf */
            rtn2 = 0;       /* return for second fscanf */
        
        /* read roomNum, respirator, bedNum and personnel */
        rtn1 = fscanf (fp, "%d %10s %d %6[^ \t\n]", &r[n].roomNum, r[n].respirator, 
                    &r[n].bedNum, r[n].personnel);
        if (rtn1 == EOF)    /* if input-failure (no conversions before EOF), break */
            break;
        
        /* loop to find next character or newline */
        for (int c = fgetc(fp); c != EOF; c = fgetc(fp))
            if (!isspace(c)) {      /* if non-whitespace character */
                ungetc (c, fp);     /* put it back in input stream */
                haspts = 1;         /* set has patients flag true */
                break;
            }
            else if (c == '\n')     /* if newline, leave has patients false */
                break;
        
        if (haspts)     /* if patient info to read, attempt to fill array */
            rtn2 = fscanf (fp, "%d,%d,%d,%d,%d", &r[n].currPatients[0],
                        &r[n].currPatients[1], &r[n].currPatients[2],
                        &r[n].currPatients[3], &r[n].currPatients[4]);
        
        /* no patients or first fscanf all conversions took place */
        if ((rtn1 && !rtn2) || (rtn1 == 4)) {
            /* validate at least 4 up to 9 total conversions took place */
            switch (rtn1 + rtn2) {
                case 9:
                case 8:
                case 7:     /* case fall-through intentional */
                case 6:
                case 5:
                case 4:
                    n++;    /* increment counter */
                    break;
            }
        }
        /* remove all characters up to '\n' */
        if (haspts)
            for (int c = fgetc(fp); c != '\n' && c != EOF; c = fgetc(fp)) {}
    }
    
    return n;   /* return number of rooms */
}

int main (int argc, char **argv)
{
    rooms build1[MAXROOMS] = {{ .roomNum = 0 }};    /* array of rooms, init all zero */
    size_t nrooms = 0;                              /* number of rooms */
    /* 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;
    }

    nrooms = aiAssign (fp, build1);                 /* read room data, save return */

    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);
    
    if (!nrooms) {      /* no rooms of data read */
        fputs ("error: nothing read from file.\n", stderr);
        return 1;
    }
    
    for (size_t i = 0; i < nrooms; i++) {   /* output all data */
        printf ("%3d  %-11s  %d  %-7s  ", build1[i].roomNum, build1[i].respirator,
                build1[i].bedNum, build1[i].personnel);
        /* possible due to initialization of array all zero at declaration */
        for (int j = 0; build1[i].currPatients[j] && j < 5; j++)
            printf (j ? ":%d" : "%d", build1[i].currPatients[j]);
        putchar ('\n');
    }

/* only hold terminal window open on windows */
#if defined (_WIN32) || defined (_WIN64)
    getchar();      /* do not use getch() (100% non-portable DOS conio.h required) */
#endif
    return 0;
}

(note: the inclusion of the ctype.h header to help when checking for whitespace with isspace() and note the removal of the archaic DOS header conio.h. You should simply use getchar() to hold the terminal open on windows instead of getch(). Including conio.h makes your program 100% NON-portable to anything but windows)

Example Use/Output

With your data in rooms.txt including the entire first line with your comment, you would have:

$ ./bin/buildrooms dat/rooms.txt
230  respirator   2  none     1000212:1000217
231  none         4  nurse    1000214
232  none         2  doctor
233  none         1  nurse    1000219

(note: the currPatients array data is output separate by ':' instead of ',')

While you can read your data with fscanf() (if you are very careful), you really shoudn't. fgets()/sscanf() provides a much easier and more robust approach. However, as an exercise, this is a pretty darn good one to teach you the consideration that go into selection and using the proper input functions as well as on the use (and limitations) of fscanf().

There is a lot involved, so take the review of the code slowly, line-by-line, expression-by-expression and make sure you understand why the code is written the way it is. If you get stuck, drop a comment below and I'm happy to help further.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thanks a lot for taking the time to provide this detailed response. I will give it a go and get back to you with an update. Really appreciate it. – Winston Hibbert Mar 27 '21 at 22:25
  • Sure, glad to help. This really is a great learning exercise -- even though it isn't the way you would want to read the file in real life. As mentioned in the answer, it's purpose is to force you to learn the `scanf()` family of functions -- and their limitations. It covers a wide swath of both. The key to what we do above is to understand `fscanf()` returns the number of **successful** conversions that take place, or `EOF` if input ends before the first successful conversion (an input-failure). If a match-failure occurs, or if input ends before all are filled, it returns less than all. – David C. Rankin Mar 28 '21 at 01:50
  • Awesome! After seeing your code it gave me a huge hint, and now I had tweaked it with some of your suggestions and I got it figured out. Thanks a lot, really appreciate it. Issue resolved. – Winston Hibbert Mar 28 '21 at 19:55
  • Great job. Learning C is more like a journey than a race, so the best way to approach the journey is to simply slow down and enjoy it. `scanf()` (and family) is a function full of pitfalls for the new C programmer. Mainly due to what happens when a *matching-failure* occurs (character extraction stops at the point of failure leaving the characters causing the failure in `stdin` unread) Another pitfall is the one at issue here, consumption of whitespace. (all are why `fgets()` is recommended for all input is reading a line-at-a-time. Great job putting the pieces together, the journey has begun. – David C. Rankin Mar 28 '21 at 20:29