Reputation: 3
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.
Upvotes: 0
Views: 357
Reputation: 84561
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.
Upvotes: 2
Reputation:
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.
Upvotes: 0