0

I need to code a program that finds student average scope. I don't understand how I can do flexible char name, if a student's last name is Константинопольский?

struct student {
  char group[5];
  char name[21];
  char exam[5];
  char test[12];
};

void student_average_scope(FILE *file) {
  struct student this;
  int i;
  int sum;

  while (fgets((char *)&this, sizeof(this), file) != NULL) {
    this.name[20] = '\0';
    this.exam[4] = '\0';

    for (i = 0, sum = 0; i < 4; i++) {
      sum += this.exam[i] - '0';
    }

    printf("%s - %.1f\n", this.name, (float)sum / 4);
  }

  fclose(file);
}

My student list:

4273 Багров Д. С. 5454 знззз
4273 Нуйикн А. А. 4333 знзнз
4272 Галкин Г. А. 5445 ззззз
4273 Иванов А. А. 3433 знззн
4272 Козлов И. И. 4443 ззззз
4272 Козлов В. И. 4444 знззз
4272 Бобров П. Н. 4543 знззз
4272 Шмелев И. И. 4443 знззн
Jongware
  • 22,200
  • 8
  • 54
  • 100
rel1x
  • 2,351
  • 4
  • 34
  • 62
  • 2
    I don't think auto-parsing a string using a struct is a good idea, and your problem is a direct effect of that choice. Just read the line eg with fgets into a character array, then parse it, eg with sscanf. – fvu Dec 13 '14 at 12:19
  • 1
    Either increase your `name` element size to allow the longest name possible (memory-inefficient but very straightforward), or make it a `char *` and use `malloc`. You'd also need to rethink your "string parsing" (try with `strtok`). – Jongware Dec 13 '14 at 12:33
  • 1
    I suggest @fvu suggestion too, but instead of `sscanf` you might be interested in `strtok`. – Iharob Al Asimi Dec 13 '14 at 12:51
  • check this one, http://stackoverflow.com/a/27455042/1983495 – Iharob Al Asimi Dec 13 '14 at 12:53
  • @fvu, Jongware and Iharob thank you so much for answers! Fvu if you'll change your comment to answer I'll select it. – rel1x Dec 13 '14 at 13:01
  • @fvu: I'd like to see a solution using `sscanf`. It's not a function I generally use, but in this case it may be better than my brute-force parsing. – Jongware Dec 13 '14 at 13:23
  • @Jongware see my answer - scanf is I think more convenient. – fvu Dec 13 '14 at 14:31
  • @iharob indeed but strtok is a lot harder, even [the manpage](http://linux.die.net/man/3/strtok) states: "Be cautious when using these functions.". :) – fvu Dec 13 '14 at 14:47

2 Answers2

3

As requested by @Jongware, a solution based on the scanf family, a very convenient means to parse strings if the input's structure is relatively constant.

Given the input string

4273 Багров Д. С. 5454 знззз

I'll assume for this example that the constant pattern we're after is an int followed by 3 strings, followd by an int and a string. There are other ways, I'll get back to these.

A very basic demo:

#include <stdio.h>

int main(void) {
    char * inputdata = "4273 Багров Д. С. 5454 знззз";
    // variables to receive the scanned data
    int firstint, secondint;
    char firststring[32];
    char secondstring[32];
    char thirdstring[32];
    char fourthstring[32];
    // important, you should check whether the number of converted elements 
    // matches what you expect:
    int scannedelements;

    // let's scan the input
    scannedelements = sscanf (inputdata,"%d %s %s %s %d %s",&firstint, &firststring, secondstring, 
                thirdstring,&secondint,fourthstring);
    // and show what we found.  Notice the similarity between scanf and printf
    // but also note the subtle differences!!!
    printf("We scanned %d %s %s %s %d %s\n",firstint, firststring, secondstring, 
                thirdstring,secondint,fourthstring);
    printf("That's a total of %d elements %d\n",scannedelements);
    return 0;
}

Output:

We scanned 4273 Багров Д. С. 5454 знззз 
That's a total of 6 elements

Notice that I scanned the field you named exam into an integer, you can easily extract digits out of it by a loop of digit = data % 10; data = data / 10;

Now, the fact that the first group of strings is chopped into 3 different outputs could be annoying. Depending on the output data, we could instruct sscanf to read until it encounters a digit:

#include <stdio.h>

int main(void) {
    char * inputdata = "4273 Багров Д. С. 5454 знззз";
    // variables to receive the scanned data
    int firstint, secondint;
    char firststring[32];
    char secondstring[32];
    char thirdstring[32];
    char fourthstring[32];
    // important, you should check whether the number of converted elements 
    // matches what you expect:
    int scannedelements;

    // Alternatively, let's scan the group of 3 strings into 1 variable
    scannedelements = sscanf (inputdata,"%d %[^0-9] %d %s",&firstint, firststring, &secondint,fourthstring);
    // and show what we found.
    printf("We scanned %d %s %d %s\n",firstint, firststring,secondint,fourthstring);
    printf("That's a total of %d elements %d\n",scannedelements);
    return 0;
}

which outputs:

We scanned 4273 Багров Д. С. 5454 знззз 
That's a total of 4 elements -1079150400 

Notice the trailing space in Багров Д. С., which may or may not be a problem, but it's easily removed.

For your convenience, this code is available on ideone: http://ideone.com/4gFlxf#sthash.KQfhcYxr.dpuf

This example barely scratches the surface of what's possible with scanf, I encourage you to explore its manpage to discover more possibilities.

--

On how to calculate the average score:

#include <stdio.h>

int main(void) {
    int inputdata = 24680;

    int average = 0;
    int number_digits = 0;
    int digit = 0;
    int digits = 0;

    while (inputdata > 0) {
        digit = inputdata % 10; // modulo by 10 is the last digit
        average += digit;
        digits++;
        inputdata = inputdata / 10; // integer division by 10 = remove last digit
    }

    if (digits > 0) { // to avoid dividing by zero is some edge case
        printf ("The average over %d scores is %.1f\n", digits, (double) average / digits);
    } else {
        printf ("As the input was 0, the average is 0");
    }

    return 0;
}
fvu
  • 32,488
  • 6
  • 61
  • 79
  • Minor note: those integers are not really *integers*; they are sequences of 4 digits. So they have to be deconstructed (fairly basic, but still). I don't think they can be read as 4 digits with `sscanf`, can they? – Jongware Dec 13 '14 at 14:34
  • @Jongware I was just adding a note concerning that topic: by converting the sequence of digits to an int you can extract the digits in a numeric way instead of converting them from char to int, which IMO is also safer and more convenient. – fvu Dec 13 '14 at 14:35
  • @fvn sorry, but I don't understand how I can find average scope now and remove space in name – rel1x Dec 13 '14 at 15:24
  • @pertpoert I added the code on how to calculate the average by using modulo and integer division - does that make it clearer? Removing leading or trailing blanks from strings is explained very well here: http://stackoverflow.com/questions/122616/how-do-i-trim-leading-trailing-whitespace-in-a-standard-way – fvu Dec 13 '14 at 15:30
1

The easiest way is to increase the size of char name to allow the longest possible student name. But reading raw data straight into a struct is very unsafe; it's better to use a temporary string and parse it.

Note that this way you don't need the struct student anymore, and also don't need to make name a char *. There is still a limitation of some kind: the read buffer must be large enough to read all of the data of each line. In itself, fgets can read 'too long' lines (they will not end with \n) but you'd need a lot of flags to be sure you are parsing the correct data. It's in this case easier to make the buffer "surely large enough".

void student_average_scope(FILE *file) {
  int i;
  int sum;
  char read_buf[128];   /* must be long enough! */
  char *name_ptr, *exam_ptr;

  while (fgets(read_buf, sizeof(read_buf), file) != NULL)
  {
    /* find name */
    name_ptr = strchr (read_buf, ' ');
    if (!name_ptr) continue;
    /* 'name_ptr' now points to last name */
    /* we need some trickery to skip zero(!) or more initials ... */
    exam_ptr = name_ptr;
    while (*exam_ptr && !isdigit(*exam_ptr))
    {
        exam_ptr++;
    }
    if (!*exam_ptr) continue;
    /* exam_ptr now points to first digit after name */
    exam_ptr[-1] = 0;

    sum = 0;
    for (i = 0; i < 4; i++) {
      sum += exam_ptr[i] - '0';
    }

    printf("%s - %.1f\n", name_ptr, (float)sum / 4);
  }

  fclose(file);
}

outputs

 Багров Д. С. - 4.5
 Константинопольский А. А. - 3.2
 Галкин Г. А. - 4.5
 Иванов А. А. - 3.2
 Козлов И. И. - 3.8
 Козлов В. И. - 4.0
 Бобров П. Н. - 4.0
 Шмелев И. И. - 3.8
Jongware
  • 22,200
  • 8
  • 54
  • 100