0

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!

6
  • Please post an minimal reproducible example Commented Sep 19, 2016 at 8:07
  • 1
    The "%c" format (with or without any modifier) will not skip leading white-space. Try putting a space in the format string before the "%*c" you use to skip the pipe characters. Also check what scanf returns. Commented Sep 19, 2016 at 8:10
  • 2
    Wouldn't be easier to read every line and then parse them? Commented Sep 19, 2016 at 8:21
  • Do you have to use scanf or can you use a normal line-oriented input function like fgets or getline and then pass the line to sscanf (or simply walk a pair of pointers through the buffer). Commented Sep 19, 2016 at 9:23
  • Current code is not testing the return value of scan() and OP is having troubles. To get past "have no idea on how to fix it.", test the return value of each scanf() against expectations. if (scanf("%*d-%*d-%d", &SSN[i]) == 1) Good() else Bad(); Commented Sep 19, 2016 at 16:31

2 Answers 2

2

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
Sign up to request clarification or add additional context in comments.

4 Comments

Good job. I was going to answer it and the formats were hurting my eyes :) BTW, why didn't you advocate fgets instead (read line by line)? That would be easy to debug, maintain.
I did advocate fgets and sscanf, both in the comment and in the intro, but alas, sometimes when the data legitimately allows for the use of fscanf itself, it is worth the example.
I didn't pay attention to your comment. I saw it just now. Upvote +1.
I added the fgets and sscanf example anyway. It's worth the time to provide the preferred alternative. Thanks.
1

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

3 Comments

Why not split last, first on the ','? (e.g. %[^,] for last)
Thanks! is there anyway I could get just the last digits of the SSN?
To gets the last 4 digits of each SSN, add %*7s (length of the ignored part, note that %s ignores space, so does %*7s) before the corresponding %s (i.e: after the \" and before the %s, so that ...%[^\"]\"%s... will become ...%[^\"]\"%*7s%s...), check out this example. If this is not what you've asked for, change the number of character to be ignored to fit your need.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.