3

The following program attempts to read an input file line by line using fgets, and save each comma delimited float value into an array of structs using sscanf (this aspect of the code works fine). The issue lies in that the program should also detect when a float value is missing/empty, and assign it the float value 1.500 which then is saved into the array of structs.

EDIT: This is supposed to be compiled using VS2017, so on Windows.

*Note: Please note that the following questions have been studied before posting this question:

How to check if a string returned by scanf is null

How to get scanf to continue with empty scanset

An example of the input file (missing value in the second row):

0.123f, 0.234f, 0.345f, 0.456f, 0.567f
1.987f, , 7.376f, 2.356f, 5.122f
9.111f, 1.234f, 7.091f, 6.672f, 9.887f

Desired output (missing value in second row is detected and set to 1.500):

0.123 0.234 0.345 0.456 0.567
1.987 1.500 7.376 2.356 5.122
9.111 1.234 7.091 6.672 9.887

So far, the first attempt tried to scan all 5 floats (each with 'f' suffix) into strings and then check to see if those strings are null/empty or of zero length using strcmp and strlen, respectively, and finally involved trying to use sscanf again on each of those variables to read each into an array of structs.

The 2nd attempt included a check to see if the sscanf was successful by using if (sscanf(line, "%ff", &data[i].x) == NULL) { // ...some alert and assign 1.500}, which did not work either. The 3rd attempt, as seen below:

#include "stdio.h"

int main() {

typedef struct {
    float x, y, vx, vy, mass;
}DATA;

    FILE *file = fopen("null_detector.txt", "r");
    if (file == NULL)
    {
        printf(stderr, "ERROR: file not opened.\n");
        return EXIT_FAILURE;
    }
    int N= 3;
    DATA* data = malloc(Nbodies * sizeof * data); // Array allocation
    char line[256];
    int i;
    int inc = 1;
    for (i = 0; i < Nbodies; i += inc)
    {
        fgets(line, sizeof(line), file);

        // **Some info:
        // Scan 5 float variables per line (this part works fine)
        sscanf(line, "%ff, %ff, %ff, %ff, %ff",
            &data[i].x, &data[i].y, &data[i].vx, &data[i].vy, &data[i].mass); // %ff accounts for 'f' suffix

        // Now check if any of above vars are empty/NULL.
        // NOTE: aware that these vars CANNOT be compared to NULL,
        // but has been included to try and provide clarity for end goal
        if (data[i].x == NULL)
        {
            //.. assign 1.500 to data[i].x
        }
        if (data[i].y == NULL)
        {
            //... same as above etc
        }
        // ...Repeat IF statements for all 5 vars

    }

     //Print the contents of array of structs to check for correct output
    for (i = 0; i < Nbodies; i++)
    {
        printf("%.3f %.3f %.3f %.3f %.3f\n", data[i].x, data[i].y, data[i].vx, data[i].vy, data[i].mass);
    }

    return 0;
}

Summary:

Does anyone know how this program can be modified to:

  • detect missing float values in each line of the file upon reading them with fgets
  • replace missing float values with the float value 1.500
  • write these values to the array of structs, like the non-missing values successfully are doing?
  • As commented in the code, I am aware that the struct float variables cannot be compared to NULL. I have included this comparison in the code to only try to add some clarity as to what the end goal is.
p.luck
  • 646
  • 2
  • 9
  • 34
  • Take a look at `strtok` – jiveturkey Apr 09 '20 at 17:27
  • 2
    @jiveturkey `strtok` doesn't scan empty fields, [strsep](http://man7.org/linux/man-pages/man3/strsep.3.html) does – David Ranieri Apr 09 '20 at 17:29
  • 1
    Nicely formed question. – chux - Reinstate Monica Apr 09 '20 at 17:29
  • That's cool. Never knew that one. – jiveturkey Apr 09 '20 at 17:31
  • 1
    @chux-ReinstateMonica yes, in fact I'm going to reopen it because I didn't pay attention to the _Please note that the following questions have been studied before posting this question:_ part, and they are not answering to how to set a default value like 1.500 – David Ranieri Apr 09 '20 at 17:36
  • 2
    Whenever you use `scanf`, without exception, *you must check its return value*. That might not be sufficient, but it is absolutely necessary. And you should understand what the return value means: it is the number of conversions stored in arguments. (It is *not* a pointer, so comparing it to `NULL` is meaningless.) – rici Apr 09 '20 at 17:47
  • 1
    You cannot reliably tell if an argument to `scanf` has had its value filled in, since C does not have any concept of an "uninitialised" value. You can compare the value of an argument with its previous value, if you saved that somewhere, but that's not teliabke; the user might happen to enter that particular value. BUT, the return value of `scanf` is clear. If `scanf` returns 0, no arguments have been set. If it returns 1, only the first argument has been set. And so on. – rici Apr 09 '20 at 17:52
  • @Rici That makes sense to me, I will play with the return value to check for 0 or 1 and post my working program when finished. – p.luck Apr 09 '20 at 18:09
  • `NULL` is for pointers, not values. – S.S. Anne Apr 09 '20 at 20:19

2 Answers2

1

You can use strsep to separate each line.

str = strsep(&line, ",")

Using one function to set the value of data:

void set_data(DATA *dt, int count, float f) {
    switch(count) {
        case 0: dt->x = f; break;
        case 1: dt->y = f; break;
        case 2: dt->vx = f; break;
        case 3: dt->vy = f; break;
        case 4: dt->mass = f; break;
    }
}

The complete code:


#include <stdio.h>  
#include <unistd.h>  
#include <string.h>  
#include <stdlib.h> 

typedef struct {
    float x, y, vx, vy, mass;
}DATA;

void set_data(DATA *dt, int count, float f) {
    switch(count) {
        case 0: dt->x = f; break;
        case 1: dt->y = f; break;
        case 2: dt->vx = f; break;
        case 3: dt->vy = f; break;
        case 4: dt->mass = f; break;
    }
}

int main() {

    FILE *file = fopen("text.txt", "r");
    if (file == NULL)
    {
        printf( "ERROR: file not opened.\n");
        return EXIT_FAILURE;
    }
    int N= 3;
    DATA* data = malloc(N * sizeof(data)); // Array allocation
    char *line;
    int i;
    int inc = 1;
    size_t n = 0;
    for (i = 0; i < N; i += inc)
    {
        getline(&line, &n, file);
        int count = 0;
        char *str;
        while((str = strsep(&line, ",")) != NULL) {
            if (strcmp(str, " ") == 0) {
                set_data(&data[i], count, 1.5);
            } else {
                set_data(&data[i], count, atof(str));
            }
           // printf("count = %d\n", count);
            // printf("token: %s\n", str);
            count++;
        }

    }

     //Print the contents of array of structs to check for correct output
    for (i = 0; i < N; i++)
    {
        printf("%.3f %.3f %.3f %.3f %.3f\n", data[i].x, data[i].y, data[i].vx, data[i].vy, data[i].mass);
    }

    return 0;
}

The input:

#cat text.txt
0.123f, 0.234f, 0.345f, 0.456f, 0.567f
1.987f, , 7.376f, 2.356f, 5.122f
9.111f, 1.234f, 7.091f, 6.672f, 9.887

The output:

0.123 0.234 0.345 0.456 0.567
1.987 1.500 7.376 2.356 5.122
9.111 1.234 7.091 6.672 9.887
Hitokiri
  • 3,607
  • 1
  • 9
  • 29
  • This looks like what I was looking for, thank you very much for your help. I'm having some trouble with `#include ` as I'm working on VS2017 (I should have mentioned this in my question). I think I can use `#include ` instead, although I recieve the following two errors when compiling with : `unresolved external symbol _getline reference in function main` and `unresolved external symbol _strsep reference in function main`. I will try to solve this and will post the solution when I am finished. Thanks again @Hitokiri – p.luck Apr 10 '20 at 14:57
  • #include does not need in this case, sorry, i did not do the attention. So you can delete include . For getline in VS, you just include instead of – Hitokiri Apr 10 '20 at 15:15
  • Great, I will give that a go. Again thank you for your help, very clear and easy to understand answer. – p.luck Apr 10 '20 at 15:53
  • Apologies @Hitokiri, I will mark it as accepted again as it was close. It only works on Linux (this was my fault for not mentioning it in the questions, as I am in fact using VS2017). I have written a function for strsep but your solution only accounts for delimeters where there is a `comma` followed by a `space`, but in my example input sometimes there are no spaces between commas. I will keep working on this. – p.luck Apr 13 '20 at 07:55
  • 1
    Try to make a funtion by yourself. Goodluck – Hitokiri Apr 13 '20 at 08:58
1

It can also achieved with only sscanf if there is at least a space between the commas when there is an absence of an input value.

#include <stdio.h>
int main(void) {
  char *str[] = {"0.123f, 0.234f, 0.345f, 0.456f, 0.567f",
                 "1.987f, , 7.376f, 2.356f, 5.122f",
                 "9.111f, 1.234f, 7.091f, 6.672f, 9.887f"};

  float float_arr[3][5];
  char temp[5][7];

  for (unsigned i = 0; i < 3; i++) {
    if (5 != sscanf(str[i], "%6[^,],%6[^,],%6[^,],%6[^,],%6[^,]", 
                    temp[0], temp[1], temp[2], temp[3], temp[4]))
      return printf("Error\n"), 1;

    for (unsigned j = 0; j < 5; j++)
      if (1 != sscanf(temp[j], "%ff", &float_arr[i][j]))
        float_arr[i][j] = 1.500f;
  }

  // printing the result
  for (unsigned i = 0; i < 3; i++) {
    for (unsigned j = 0; j < 5; j++)
      printf("%ff ", float_arr[i][j]);
    printf("\n");
  }
  return 0;
}

Output

0.123000f 0.234000f 0.345000f 0.456000f 0.567000f 
1.987000f 1.500000f 7.376000f 2.356000f 5.122000f 
9.111000f 1.234000f 7.091000f 6.672000f 9.887000f 
Eraklon
  • 4,206
  • 2
  • 13
  • 29