Josh Martin
Josh Martin

Reputation: 340

using scanf to read a text in C programming

This is for my C programming class and my teacher is making use scanf and we can not use anything else. I am passing in this text file: https://gist.github.com/cjoshmartin/29bd3365a925ee295da21ae2e917c7e1 I am using the commend line to pass in the file with this commend: program1 < lab4text.txt

here is what I am trying to get for output:

   | SSN   |   Average   |  263? |
   ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐
1  | xxxx  |   xx.xxx%   |   Y   |
2  | xxxx  |   xx.xxx%   |   N   |
3  | xxxx  |   xx.xxx%   |   Y   |
4  | xxxx  |   xx.xxx%   |   Y   |



here is  what is what I currently am getting:
   |  SSN | Average |  263 |
-------------------------
 1  | 2381  | 67.454 %  |     Y |
 2  |    0  | -167.040 %  |     2 |
 3  |    0  | 3762321554297869312.000 %  |     6 |
 4  | 1234  | 81.318 %  |     Y |

I think the scanf is reading from the wrong part of the text file but I have no idea on how to fix it.

here is my current code:

#include <stdio.h>
 void main(void){

//FILE *file;
int i = 0;
int SSN[4] = { 0 }, j;
char isin263[4];
float quizavg[4];
float labavg[4];
float exam1[4];
float exam2[4];
float finalexam[4];
scanf("%*[^\n]\n");
scanf("%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s");
scanf("%*[^\n]\n");


for (i = 0;i < 4;i++)
{
    scanf("%*s %*s %*s");
    scanf("%*d-%*d-%d", &SSN[i]);
    scanf(" %*9s%*c%*c%*c%*c %c %f %f %f %f %f\n", &isin263[i], &quizavg[i], &labavg[i], &exam1[i], &exam2[i], &finalexam[i]);
}


    printf("|%5s | %5s | %4d |\n","SSN","Average", 263);
    printf("-------------------------\n");
    for (j = 0;j < 4;j++)
    {
        //fixed this line
        printf(" %i  | %4d  | %5.3f %%  | %5c |\n",j+1,SSN[j], (quizavg[j]*0.07)+(labavg[j]*0.24)+((exam1[j]+exam2[j])*0.185)+(finalexam[j]*0.32), isin263[j]);
    }


  }

any help would be greatly appreciated!

Upvotes: 0

Views: 1869

Answers (2)

David C. Rankin
David C. Rankin

Reputation: 84561

If you are going to attempt to read the varying data with fscanf, then the format string will be the key to your success (or failure). If each of the rows you need to collect have an identical format, then you can attempt to read the data with fscanf (while you gain flexibility by decoupling your read from your parse & validation by using fgets and sscanf). However, as long as you have a way to keep the lines you need, and skip those that you don't, then fscanf is available as a tool.

In this case with your example data LYF_HKN has done a good job in his answer accounting for all the information in each of the lines you need. I would just add a small variant and use something like:

char *fmt = " %*[^\"]\"%[^,], %[^\"]\" %s %s %[YN] %lf %lf %lf %lf %lf";

The rest is simply looping over the lines in your input file and indexing the students you add to your struct. You will need to capture the return of fscanf and compare it with your needed match count of 10 to validate that each of your values received input. You also need to skip the header line and any other lines not holding student data. You can accomplish this by reading so long as fscanf does not encounter EOF setting an error condition on the stream. You then simply check the match count (the return) against your anticipated 10 to indicate whether it was a valid line of student data or not.

Putting this together, you could do something like the following:

#include <stdio.h>

/* constants for use in your code */
enum { YN = 2, DOB = 11, SS = 12, NM = 32 };

typedef struct {
    char last[NM], first[NM], ss[SS], dob[DOB], yn[YN];
    double qavg, lavg, ex1, ex2, fex;
} stnt;

int main (int argc, char **argv) {

    stnt s[8] = {{ .first = "" }};
    int cnv = 0, n = 0;
    char *fmt = " %*[^\"]\"%[^,], %[^\"]\" %s %s %[YN] %lf %lf %lf %lf %lf";
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    /* read each line, if the match-count is 10 increment index */
    while ((cnv=fscanf (fp, fmt, s[n].last, s[n].first, s[n].ss, s[n].dob, s[n].yn,
                &s[n].qavg, &s[n].lavg, &s[n].ex1, &s[n].ex2, &s[n].fex)) != EOF)
        if (cnv == 10) n++;

    if (fp != stdin) fclose (fp);     /* close file if not stdin */

    for (int i = 0; i < n; i++) /* output the student information */
        printf ("\n student   : %s %s\n ss number : %s\n D.O.B.    : %s\n"
                " yes/no    : %s\n quiz avg  : %.2lf\n lab avg   : %.2lf\n"
                " exam 1    : %.2lf\n exam 2    : %.2lf\n final     : %.2lf\n",
                s[i].first, s[i].last, s[i].ss, s[i].dob, s[i].yn, s[i].qavg,
                s[i].lavg, s[i].ex1, s[i].ex2, s[i].fex);

    return 0;
}

Using fgets and sscanf

Given the discussion and the comments regarding the use of fgets and sscanf, it is worth dropping a short example making use of that approach, which I prefer, in addition to the fscanf example. The code is roughly the same except for the addition of a buffer buf to hold the line of user input and the control of the loop based on the return of fgets. Other than that, the replacement of fscanf with sscanf is nothing more than substituting the buffer in place of the file stream in the sscanf call. Both fscanf and sscanf return the match count being the number of successful conversions that took place based on the conversion specifiers present in the format string.

#include <stdio.h>

/* constants for use in your code */
enum { YN = 2, DOB = 11, SS = 12, NM = 32, MAXC = 256 };

typedef struct {
    char last[NM], first[NM], ss[SS], dob[DOB], yn[YN];
    double qavg, lavg, ex1, ex2, fex;
} stnt;

int main (int argc, char **argv) {

    stnt s[8] = {{ .first = "" }};
    int n = 0;
    char buf[MAXC] = "",
        *fmt = " %*[^\"]\"%[^,], %[^\"]\" %s %s %[YN] %lf %lf %lf %lf %lf";
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    /* read each line, if the match-count is 10 increment index */
    while (fgets (buf, MAXC, fp))
        if (sscanf (buf, fmt, s[n].last, s[n].first, s[n].ss, s[n].dob, s[n].yn,
                &s[n].qavg, &s[n].lavg, &s[n].ex1, &s[n].ex2, &s[n].fex) == 10)
            n++;

    if (fp != stdin) fclose (fp);     /* close file if not stdin */

    for (int i = 0; i < n; i++) /* output the student information */
        printf ("\n student   : %s %s\n ss number : %s\n D.O.B.    : %s\n"
                " yes/no    : %s\n quiz avg  : %.2lf\n lab avg   : %.2lf\n"
                " exam 1    : %.2lf\n exam 2    : %.2lf\n final     : %.2lf\n",
                s[i].first, s[i].last, s[i].ss, s[i].dob, s[i].yn, s[i].qavg,
                s[i].lavg, s[i].ex1, s[i].ex2, s[i].fex);

    return 0;
}

Example Use/Output

Using your data (with either), you would end up with output similar to the following:

$ ./bin/rdstudents <dat/lab4text.txt

 student   : Christopher Jones
 ss number : 162-74-2381
 D.O.B.    : 9/12/1995
 yes/no    : Y
 quiz avg  : 51.67
 lab avg   : 72.50
 exam 1    : 77.00
 exam 2    : 68.50
 final     : 61.00

 student   : Sarah Lee Abrahamson
 ss number : 127-49-0853
 D.O.B.    : 11/5/1993
 yes/no    : N
 quiz avg  : 87.10
 lab avg   : 79.33
 exam 1    : 64.25
 exam 2    : 84.00
 final     : 72.50

 student   : Adreana Parker-Jones
 ss number : 230-38-1234
 D.O.B.    : 3/1/1996
 yes/no    : Y
 quiz avg  : 75.23
 lab avg   : 81.04
 exam 1    : 78.50
 exam 2    : 80.00
 final     : 85.25

 student   : Joshua Ellis
 ss number : 186-27-1372
 D.O.B.    : 7/31/1988
 yes/no    : Y
 quiz avg  : 85.23
 lab avg   : 94.90
 exam 1    : 85.00
 exam 2    : 92.00
 final     : 94.25

Upvotes: 2

DDMC
DDMC

Reputation: 393

Maybe this is what you're looking for.

Specifier: %*s%*[ \"]%[^,], %[^\"]\"%s%s%*[ ]%[YN]%lf%lf%lf%lf%lf
    %*s       - Skips '*' characters at the beginning of each line
    %*[ \"]   - Skips spaces and the opening quote
    %[^,]     - Gets the Last Name
    ', '      - Ignores the comma and space
    %[^\"]    - Gets the First Name
    '"'       - Ignores the closing quote
    %s        - Gets the SSN
    %s        - Gets the DOB
    %*[ ]     - Ignores spaces
    %[YN]     - Gets the value of the 263? column
    %lf       - Gets the Quiz-avg
    %lf       - Gets the Lab-avg
    %lf       - Gets the Exam#1
    %lf       - Gets the Exam#2
    %lf       - Gets the Final Exam

Upvotes: 1

Related Questions