0

Good day to you all. I was wondering why the program I wrote in C is not working properly. I am trying to read integers from a text file and put it in a 2 Dimensional array based on the number of integers in the text file. I want to count the integers and print them on the console to make sure everything is working; My code is can be seen here:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main()
{
    FILE *fp;
    fp = fopen("COSC450_P1_Data.txt", "r");
    char str[200];
    int words;
    int row, columns, count;
    if(fp == NULL)
    {
        printf("Could not open file %s", fp);
        return 2;
    }

    while(getc(fp) != EOF)
    {
        count++;
        fgets(str, 200, fp);
        puts(str);
    }
    printf("%d  ", count);
    fclose(fp);

    return 0;
}

Here is what the file looks like:

O123 34 -54 21 45 34 65 -54 21 45 34 65 -34 24 58

49 45 10 -57 20

57 39 20 58 23 10 20 58 -60 76 -82 28

  28 -37 49 358 47 -50 37 29

57 -29 -20 47 69

93 57 23 49 -38 49 27 -40 48 39

56 -30 47 28 49

37 49

  27 26 10 20 58 -60 26 10 20 58 -60 76   -82 28

  28 -37 49 -28 93 28

73 47 27 83 37 -29 40 37 49 20

17 -26 12 17 17

18 38 29 39 -118

19 10 20 58 -60 76 -82 28

  28 -37 49 59 10 58 -60 76   -82 28

  28 -37 49 59 10 20 58 -60 76   -82 28

  28 -37 49 30 -58 58     38 49 30 -58 58     38

49 30 -58 58 38

28 39

39 48 23 -50 28

48 29 39 40 29

The next image is what the output looks like:

3 34 -54 21 45 34 65 -54 21 45 34 65 -34 24 58

49 45 10 -57 20

7 39 20 58 23 10 20 58 -60 76 -82 28

    28 -37 49 358 47 -50 37 29

7 -29 -20 47 69

93 57 23 49 -38 49 27 -40 48 39

6 -30 47 28 49

7 49

    27 26 10 20 58 -60 26 10 20 58 -60 76   -82 28

    28 -37 49 -28 93 28

3 47 27 83 37 -29 40 37 49 20

7 -26 12 17 17

8 38 29 39 -118

9 10 20 58 -60 76 -82 28

    28 -37 49 59 10 58 -60 76   -82 28

    28 -37 49 59 10 20 58 -60 76   -82 28

    28 -37 49 30 -58 58     38 49 30 -58 58         38

9 30 -58 58 38

8 39

9 48 23 -50 28

8 29 39 40 29

83

The output cuts off certain numbers and its giving me the incorrect total number of integers. Please help. Thank you and have a nice day.

Update: Thank you for the help. Unfortunately, changing the while loop while(getc(fp) != EOF) to while(fgets(fp) != NULL) causes the program to skip the first line, as seen here:

       49 45 10     -57 20

    28 -37 49 358 47 -50 37 29

    93 57   23 49 -38 49            27 -40 48 39

37 49

    28 -37 49 -28 93 28

17 -26 12 17 17

19 10 20 58 -60 76 -82 28

    28 -37 49 59 10 20 58 -60 76   -82 28

49 30 -58 58 38

39 48 23 -50 28

48 29 39 40 29 73

Removing fgets(str, 200, fp) from the body of the while loop, however, does provide a more accurate output, but not what I want:

23 34 -54 21 45 34 65 -54 21 45 34 65 -34 24 58

       49 45 10     -57 20

57 39 20 58 23 10 20 58 -60 76 -82 28

    28 -37 49 358 47 -50 37 29

57 -29 -20 47 69

    93 57   23 49 -38 49            27 -40 48 39

56 -30 47 28 49

37 49

    27 26 10 20 58 -60 26 10 20 58 -60 76   -82 28

    28 -37 49 -28 93 28

73 47 27 83 37 -29 40 37 49 20

17 -26 12 17 17

18 38 29 39 -118

19 10 20 58 -60 76 -82 28

    28 -37 49 59 10 58 -60 76   -82 28

    28 -37 49 59 10 20 58 -60 76   -82 28

    28 -37 49 30 -58 58     38 49 30 -58 58         38

49 30 -58 58 38

28 39

39 48 23 -50 28

48 29 39 40 29 83

Please let me know if you have more suggestions. Thank you all again.

  • 2
    Posting text here as text is preferred and more useful than posting text as a picture with a link. It makes for more clarity, more up votes and less down votes. – chux - Reinstate Monica Nov 04 '18 at 01:53
  • 1
    Maybe you should change you `while` loop to: `while(fgets(str, 200, fp) != NULL)` – Fiddling Bits Nov 04 '18 at 01:54
  • 1
    "The output cuts off certain numbers" because your code reads the first character of the line with `getc(fp)` and the rest with `fgets(str, 200, fp);`. – chux - Reinstate Monica Nov 04 '18 at 01:58
  • Thank you. This is my first time messing with C, so there is a lot of things that I am not familiar with. I am trying to learn as I go. @Fiddling Bits So I tried changing 'while' loop to 'while(fgers(str, 200, fp) != NULL)' and it only made things worse.... now it is not reading the first line in the text file... – Kashif Gbajabiamila Nov 04 '18 at 02:26
  • What is the maximum number of characters in a line, 200? – Fiddling Bits Nov 04 '18 at 02:29
  • 1
    @FiddlingBits sorry there is no maximum; at least none that I aware of. That number is just my approx. of how large the incoming input from the file was. – Kashif Gbajabiamila Nov 04 '18 at 02:40

2 Answers2

2

The output cuts off certain numbers and its giving me the incorrect total number of integers.

Of course it does. You are reading and removing a character from your input stream with:

while(getc(fp) != EOF)
    ...

and then attempting to read the entire line of data as if nothing had ever been removed:

fgets(str, 200, fp);

fgets will read up to (and including) the trailing '\n' and include the '\n' in the buffer it fills -- so you are essentially chopping the first character off every line.

Read Once, Convert All Values In Line

You are better served simply reading each line of input into a buffer (as you attempt to do with str and then using either strtol (recommended) or sscanf with a saved offset or updated pointer to step through the buffer picking out integer values. You should be commended for taking a line-oriented approach with fgets, that is correct, you just can't remove the first-character in each line before you call it.

You have additional considerations as well. Valid integers can also begin with +/- so you need to account for sign as well. (beyond that is the question of handling octal or hexadecimal values that could begin with o/O or 0x/0X)

Using strtol

strtol is made for working through a buffer and converting numeric values found. The prototype for strtol is:

long int strtol(const char *nptr, char **endptr, int base);

where nptr is a pointer to the beginning of the conversion and critically, endptr will be updated upon the successful conversion of digits to point to one past the last digit converted allowing you to loop and convert the next value by updating nptr = endptr; after your validations are compete.

As with any code, the validations are key. For strtol you must check that nptr != endptr to determine whether any digits were converted. Then you must validate that errno == 0 and was not set due to an error with the conversion (e.g. value out of range for type, overflow, etc..).

Since you may have characters other than whitespace between the integer values in the input file, you simply scan forward in the buffer from endptr until you find the next valid beginning character for a number (e.g. +/- and digits 0-9, note you would have to additionally check of o/O if you were including octal values)

A short example using strtol that handles decimal and hexadecimal and simply output the values converted would be:

#include <stdio.h>
#include <stdlib.h>     /* for strtol */
#include <string.h>     /* for strncpy */
#include <errno.h>      /* for errno */

#define MAXC 1024   /* constant - max chars in line */

int main (void) {

    char str[MAXC] = "";    /* str to hold line, initialized all zero */

    while (fgets (str, MAXC, stdin)) {  /* read each line of input */
        char *p = str,      /* pointer for strtol */
            *endptr = NULL; /* end pointer for strtol */

        while (*p) {    /* work down str parsing integer or hex values */
            long val = strtol (p, &endptr, 0);  /* convert from p */

            /* validate conversion */
            if (p != endptr) {  /* were digits converted? */
                if (!errno) {   /* if errno 0, successful conversion */
                    char ascii[MAXC] = "";  /* for string converted */

                    strncpy (ascii, p, endptr - p); /* copy to ascii */
                    ascii[endptr-p] = 0;    /* nul-terminate ascii */

                    /* test whether string begins "0x" or "0X", output */
                    if (*p == '0' && (*(p + 1) == 'x' || *(p + 1) == 'X'))
                        printf ("hex conversion:  %-10s %10lu  0x%lx\n",
                                ascii, val, val);
                    else
                        printf ("int conversion:  %-10s % ld\n",
                                ascii, val);
                }
                p = endptr; /* advance p to 1-past end of converted string */
            }

            /* find start of next valid number in str, including (+/-) */
            for (; *p; p++) {
                if ('0' <= *p && *p <= '9')  /* positive value */
                    break;          /* explicitly signed value */
                if ((*p == '+' || *p == '-') && '0' <= *(p+1) && *(p+1) <= '9')
                    break;
            }
        }
    }

    return 0;
}

(note: if you are storing the values as int, you must further check that INT_MIN <= val && val <= INT_MAX before making the assignment due to the storage type (int) differing from the conversion type (long))

Example Use/Output

With your data file in integermess.txt and simply redirecting the file to stdin you would get the following output:

$ ./bin/fgets_strtol_any <debug/dat/integermess.txt
int conversion:  123         123
int conversion:  34          34
int conversion:  -54        -54
int conversion:  21          21
int conversion:  45          45
int conversion:  34          34
int conversion:  -54        -54
int conversion:  21          21
...
int conversion:  48          48
int conversion:  29          29
int conversion:  39          39
int conversion:  40          40
int conversion:  29          29

for a total of 160 integers found.

Using sscanf

With sscanf you do much the same thing using the "%n" specifier to determine the number of characters consumed with the numeric conversion that you then use to update your position within your buffer for the next conversion.

An example that reads from the filename given as the first argument to the program (or from stdin by default if no argument is given) handling only decimal values could be similar to:

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

#define MAXC 1024

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

    char buf[MAXC] = "";    /* buffer to hold MAXC chars at a time */
    int nval = 0;           /* total number of integers found */
    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;
    }

    while (fgets (buf, MAXC, fp)) {

        char *p = buf;      /* pointer to line */
        int val,            /* int val parsed */
            nchars = 0;     /* number of chars read */

        /* while chars remain in buf and a valid conversion to int takes place
         * output the integer found and update p to point to the start of the
         * next digit.
         */
        while (*p) {
            if (sscanf (p, "%d%n", &val, &nchars) == 1) {
                printf (" %d", val);
                if (++nval % 10 == 0)     /* output 10 int per line */
                    putchar ('\n');
            }
            p += nchars;        /* move p nchars forward in buf */

            /* find next number in buf */
            for (; *p; p++) {
                if (*p >= '0' && *p <= '9') /* positive value */
                    break;
                if (*p == '-' && *(p+1) >= '0' && *(p+1) <= '9') /* negative */
                    break;
            }
        }
    }
    printf ("\n %d integers found.\n", nval);

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

    return 0;
}

Example Use/Output

Similarly using sscanf (and slightly different output formatting) you would get:

$ ./bin/fgets_sscanf_int_any_ex <debug/dat/integermess.txt
 123 34 -54 21 45 34 65 -54 21 45
 34 65 -34 24 58 49 45 10 -57 20
 57 39 20 58 23 10 20 58 -60 76
 -82 28 28 -37 49 358 47 -50 37 29
 57 -29 -20 47 69 93 57 23 49 -38
 49 27 -40 48 39 56 -30 47 28 49
 37 49 27 26 10 20 58 -60 26 10
 20 58 -60 76 -82 28 28 -37 49 -28
 93 28 73 47 27 83 37 -29 40 37
 49 20 17 -26 12 17 17 18 38 29
 39 -118 19 10 20 58 -60 76 -82 28
 28 -37 49 59 10 58 -60 76 -82 28
 28 -37 49 59 10 20 58 -60 76 -82
 28 28 -37 49 30 -58 58 38 49 30
 -58 58 38 49 30 -58 58 38 28 39
 39 48 23 -50 28 48 29 39 40 29

 160 integers found.

You have other options as well. You can read the entire file with fgetc character-by-character picking out the numeric values and manually converting the characters to numeric value by multiplying by 10 and adding (or by 8 or 16 in the case of octal and hex). However, you are much better off using a well-tested library function than one you just happen to come up with on the fly...

Look things over and let me know if you have further questions. You were largely on the right track, you just hadn't figured out the conversion yet and you were chopping the first character off each line.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thanks soo much for your help. I do not completely follow the output part for printing in the console. I used the sscanf method you provided, but when I try to get output, it only prints the number 1. The path I used is as follows: $ ./bin/fgets_sscanf_int_any_ex – Kashif Gbajabiamila Nov 04 '18 at 18:39
  • Thanks soo much for your help. You're help is really appreciated. I will say, however that I couldn't figure out the file path I needed to use, so I just removed _argc > 1_ and _stdin_ from **FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;** – Kashif Gbajabiamila Nov 04 '18 at 19:04
  • Oh, that is just a shortcut that uses the *ternary* operator (e.g. `test ? if_true : if_false;`) to optionally take the filename provided as `argv[1]` to read from, or if none is provided (meaning `test` of `argc > 1` will be false) it just assigns `stdin` to `fp` to read from `stdin` by default. Good luck with your coding. – David C. Rankin Nov 04 '18 at 23:57
0

You might want to change the loop to:

while(!feof(fp))

And change the 200 to 100. 200 character per line is a bit too many I think.

  • Thank you for your help. Unfortunately, changing the while loop statement introduced an infinite loop into the program. It just keeps scrolling down my console, without printing anything. My previous while stills works so I will revert to that for the time being. Thank you again! – Kashif Gbajabiamila Nov 04 '18 at 03:53
  • You might want to read about [why `while (!feof(file)) {}` is always wrong.](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – ad absurdum Nov 04 '18 at 04:29