alik33
alik33

Reputation: 141

Read integers only, from stdin

I am dealing with part of a program which have to read line from stdin which looks like:

{ 1, 7, 22, 4, 7, 5, 11, 9, 1 }

And I have to store only integers for the program to work with them later.

The most primitive method I am thinking of is doing something like putting while loop into program and if

getchar() == ',' or getchar() == '}'

Then continue or end reading and if there is not either ', ' or '}', then read int.

Can you think of something better?

Upvotes: 1

Views: 1757

Answers (2)

David C. Rankin
David C. Rankin

Reputation: 84561

I have always been of the mindset that writing a general input routine to handle numeric input regardless of the format makes more sense that writing 5 different routines to handle 5 different formats, etc. strtol is tailor made to handle conversion of numeric input contained in a string regardless of the format. The declaration is:

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

The fact that it takes a pointer as input, and updates an endptr to the next character following the number converted in nptr allows you to start at the beginning of a line and work to its end, converting each number found -- regardless of the format the input is in. This allows creation of a flexible input parsing function with robust error checking.

A short example below will illustrate this approach. The example will read from stdin by default or from any filename provided as the first argument. The strtol call is wrapped in a short xstrtol function that simply provides the normal error checking for each conversion. Placing it in a function prevents cluttering the main logic of your code with the repetitive error checking. Example:

#include <stdio.h>
#include <stdlib.h> /* for strtol   */
#include <limits.h> /* for INT_MIN/INT_MAX */
#include <errno.h>  /* for errno    */

#define MAXL 256

long xstrtol (char *p, char **ep, int base);

int main (int argc, char **argv)
{
    int values[MAXL] = {0};
    char line[MAXL] = {0};
    size_t i, idx = 0;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) { /* validate file open */
        fprintf (stderr, "error: file open failen '%s'.\n", argv[1]);
        return 1;
    }

    /* read each line in file (up to MAXL chars per-line) */
    while (fgets (line, MAXL, fp)) {

        char *p = line;
        char *ep = p;
        errno = 0;

        /* convert each string of digits into number */
        while (errno == 0) {

            /* skip any non-digit characters */
            while (*p && ((*p != '-' && (*p < '0' || *p > '9')) ||
                (*p == '-' && (*(p+1) < '0' || *(p+1) > '9')))) p++;
            if (!*p) break;

            /* convert string to number */
            values[idx++] = (int)xstrtol (p, &ep, 10);

            if (errno) break;   /* check for error */

            if (idx == MAXL) {  /* check if array full */
                fprintf (stderr, "warning: MAXL values read.\n");
                break;
            }

            /* skip delimiters/move pointer to next digit */
            while (*ep && *ep != '-' && (*ep < '0' || *ep > '9')) ep++;
            if (*ep)
                p = ep;
            else  /* break if end of string */
                break;
        }
    }
    if (fp != stdin) fclose (fp);

    printf ("\nvalues read from '%s'\n\n", argc > 1 ? argv[1] : "stdin");
    for (i = 0; i < idx; i++)
        printf ("  values[%3zu] : %d\n", i, values[i]);

    return 0;
}

/** a simple strtol implementation with error checking.
 *  any failed conversion will cause program exit. Adjust
 *  response to failed conversion as required.
 */
long xstrtol (char *p, char **ep, int base)
{
    errno = 0;

    long tmp = strtol (p, ep, base);

    /* Check for various possible errors */
    if ((errno == ERANGE && (tmp == LONG_MIN || tmp == LONG_MAX)) ||
        (errno != 0 && tmp == 0)) {
        perror ("strtol");
        exit (EXIT_FAILURE);
    }

    if (*ep == p) {
        fprintf (stderr, "No digits were found\n");
        exit (EXIT_FAILURE);
    }

    return tmp;
}

Input Files - 5 Different Formats

$ cat dat/10int_braces.txt
{ 8572, -2213, 6434, 16330, 3034, 12346, 4855, 16985, 11250, 1495 }

$ cat dat/10int.csv
8572, -2213, 6434, 16330, 3034
12346, 4855, 16985, 11250, 1495

$ cat dat/10int_space.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495

$ cat dat/10int_nl.txt
8572
-2213
6434
16330
3034
12346
4855
16985
11250
1495

$ cat dat/10int_5x2.txt
[[  8572  -2213  ]
 [  6434  16330  ]
 [  3034  12346  ]
 [  4855  16985  ]
 [ 11250   1495  ]]

$ cat dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a

- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495

Use/Output

All formats provide the same output, e.g.:

$ ./bin/fgets_xstrtol_simple <dat/10int_braces.txt

values read from 'stdin'

  values[  0] : 8572
  values[  1] : -2213
  values[  2] : 6434
  values[  3] : 16330
  values[  4] : 3034
  values[  5] : 12346
  values[  6] : 4855
  values[  7] : 16985
  values[  8] : 11250
  values[  9] : 1495

You can easily add dynamic allocation/reallocation as needed. Let me know if you have any questions.

Upvotes: 1

tdao
tdao

Reputation: 17668

Don't bother with getchar, you can try scanf

int d1,d2,d3;
scanf( "{%d,%d,%d}", &d1, &d2, &d3);
printf( "d1 = %d, d2 = %d, d3 = %d\n", d1, d2, d3 );

Given your input from stdin is in this format { 1, 7, 22 }, you'll get what you want in d1, d2, and d3.


In case number of input not fixed, you can try a combination of fgets and strtok:

char input_line[1000];
fgets( input_line, sizeof( input_line ), stdin );
char *pch = strtok (input_line,"{, \t}");
printf( "%d \n", atoi( pch ) );
while (pch != NULL)
{
    pch = strtok (NULL, "{, \t}");
    if( pch != NULL && pch[0] != '\n' )
    {
        printf( "%d \n", atoi( pch ) );
    }
}

If input is { 1, 7, 22, 4, 7, 5, 11, 9, 1 }, you'll get:

{ 1, 7, 22, 4, 7, 5, 11, 9, 1}      
1 
7 
22 
4 
7 
5 
11 
9 
1

Upvotes: 0

Related Questions