0

I was given 3 arrays and the input for each array is given in a single line with space between each element.

Example input:

3 2 1 1 1
4 3 2
1 1 4 1

So what I am trying to do is to assign all the elements of first line to array 1, second line to array 2 and third line to array 3.

#include <stdio.h>
int main()
{
    int a[20],b[20],c[20],d[3];
    int k=0;
    char temp;
    do{
        scanf("%d%c", &a[k], &temp); 
        k++; 
        } while(temp != '\n');
    d[0]=k;
    k=0;
    do{
        scanf("%d%c", &b[k], &temp); 
        k++; 
        } while(temp != '\n');
    d[1]=k;
    k=0;
    do{
        scanf("%d%c", &c[k], &temp); 
        k++; 
        } while(temp != '\n');
    d[2]=k;
    return 0;
}

This is what I tried, but this code saves all the elements in the first array itself. Any help?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Rama Krishna
  • 49
  • 1
  • 6
  • 2
    I suggest you use a *debugger* (and generally [learn how to debug your programs](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/)) to step through your code to make sure that the input you read is actually what you think it is. – Some programmer dude Jan 11 '18 at 14:45
  • 2
    As a possible way to solve your problem though, consider reading *full lines* and then extracting the numbers from the lines. – Some programmer dude Jan 11 '18 at 14:46
  • If you care about line breaks, don't use `scanf()` — it doesn't care about line breaks. Use `fgets()` or POSIX `getline()` and then `sscanf()`. Your attempt to handle newlines with the trailing `%c` is fragile. A trailing blank on a line wrecks the scheme. You should also check the return value from `scanf()` to make sure you did get two values read. – Jonathan Leffler Jan 11 '18 at 16:08
  • Based on your description, you're probably not entering the data in the exact format that your format string specifies or your not seeing a newline character immediately after the final integer is entered. That causes your loop to get completely out of sync with what scanf is returning, if it returns at all. – jwdonahue Jan 11 '18 at 16:19

2 Answers2

1

I've just tried your code and it works fine - a, b, c are filled with the numbers entered via stdin.

However, your primary problem is that scanf is not line oriented. You should instead use fgets to read the line in a string and parse it with strtok and sscanf.

cs95
  • 379,657
  • 97
  • 704
  • 746
Maku
  • 199
  • 2
  • 15
  • 3
    This is easily misread as a comment, not an answer. Your first paragraph is material for a comment, not an answer. The second paragraph does, however, outline a reasonable solution. – Jonathan Leffler Jan 11 '18 at 16:11
  • 1
    Note [Using `sscanf()` in loops](https://stackoverflow.com/questions/3975236/how-to-use-sscanf-in-loops) as a way to avoid the perils of `strtok()`. – Jonathan Leffler Jan 11 '18 at 16:12
0

Taking the recommendation to use fgets is one thing, putting it into use the first time is quite another. You use fgets (or POSIX getline) because they provide a mechanism for reading an entire line of text into a buffer at once. This eliminates the pitfalls inherent in trying to use scanf for that purpose.

While POSIX getline will handle a line of any length for you, it dynamically allocates storage for the resulting buffer. fgets on the other hand will read only as many characters as can be stored in the size you specify in the fgets call (reserving space for the nul-character, as fgets always provides a nul-terminated buffer)

This means it is up to you to check that a complete line fit into the buffer you provided for fgets use. Essentially you want to check whether the buffer is full and the last character is not the '\n' character. Note, you are not concerned with trimming the trailing newline here, just in checking for its presence to validate whether a complete line was read. So here you can check whether the length of buffer is your max size (minus 1 for the nul-character) and the last character is not '\n'. If those two conditions exist, you have no way of knowing whether the entire line was read (but see the note after this example). A simple approach to the validation whether a full line was read into buf is, e.g.

    while (fgets (buf, MAXC, fp)) {
        ...
        size_t len = strlen (buf);  /* length for line validation */
        /* validate whole line read into buf - exit on error */
        if (len == MAXC - 1 && buf[len - 1] != '\n') {
            fprintf (stderr, "error: line %d too long.\n", row + 1);
            return 1;
        }

(note: for the corner-case of a file without a POSIX eof (end-of-file), e.g. without a '\n' following the last line of text, there is a chance you could actually read an exact buffer full of characters and have no trailing '\n', but still have a complete read -- you can check for EOF with a call to getchar() and return the character to the buffer with putchar if it is other than EOF)

Now on to handling your arrays. Rather than declaring separate arrays of 20 int each, instead declare a 2D array of n row of 20 int each. This makes handling the read and indexing much easier.

You also have the problem of having to capture the number of values you store in each row. While you can do a little indexing magic and store the number of values in each row as the first-column value, it is probably a bit easier just to have a separate array of n values where each index corresponds to the number of values store for each row in your 2D array. For example,

    int row = 0,                    /* row count during read */
        idx[ROWS] = {0},            /* array holding col count per row */
        arr[ROWS][COLS] = {{0}};    /* 2D array holding each line array */

That way, each time you add a value to one of your rows, you simply increment the corresponding value in idx, e.g.

            /* fill a value in row, then */
            idx[row]++;             /* update col-index for array */

With that background, you are finally ready to start filling your array. The approach is straight-forward. You will:

  1. use an outer loop reading a complete line using fgets (buf, MAXC, fp);
  2. initialize inner loop variable (for offset, etc.);
  3. check that a complete line was read (as shown above);
  4. use an inner loop over buf using sscanf to repeatedly parse a single-integer from buf until all integers are read;
  5. (really 4(a.)) (you call sscanf on buf + offset from the beginning), saving the number characters consumed (saved with the %n format specifier to update offset);
  6. update offset with the number of characters consumed, and repeat.

(note: it is up to you to protect your array bounds to make sure you do not attempt to store more integer values in each array than you have storage for, and that you do not try and store more rows than you have storage for. So on each the outer and inner loop you will add a check to limit the number of rows and columns you read to the available storage)

Your read loops implementing the steps above could look like the following:

/* constants for max rows, cols, and chars for read buf */
enum { ROWS = 4, COLS = 20, MAXC = 512 };
...
    while (row < ROWS && fgets (buf, MAXC, fp)) {   /* read each line */
        int col = 0,        /* col being filled */
            nchr = 0,       /* no. chars consumed by sscanf */
            offset = 0,     /* offset in buf for next sscaf call */
            tmp = 0;        /* temp var to hold sscanf conversion */
        size_t len = strlen (buf);  /* length for line validation */
        /* validate whole line read into buf - exit on error */
        if (len == MAXC - 1 && buf[len - 1] != '\n') {
            fprintf (stderr, "error: line %d too long.\n", row + 1);
            return 1;
        }
        while (col < COLS &&    /* read each value in line into arr */
                sscanf (buf + offset, "%d%n", &tmp, &nchr) == 1) {
            arr[row][col++] = tmp;  /* assign tmp to array */
            offset += nchr;         /* update offset in buffer */
            idx[row]++;             /* update col-index for array */
        }
        row++;  /* increment row for next read */
    }

Putting it altogether, you could do something like the following:

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

/* constants for max rows, cols, and chars for read buf */
enum { ROWS = 4, COLS = 20, MAXC = 512 };

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

    int row = 0,                    /* row count during read */
        idx[ROWS] = {0},            /* array holding col count per row */
        arr[ROWS][COLS] = {{0}};    /* 2D array holding each line array */
    char buf[MAXC] = "";            /* buffer for fgets */
    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 (row < ROWS && fgets (buf, MAXC, fp)) {   /* read each line */
        int col = 0,        /* col being filled */
            nchr = 0,       /* no. chars consumed by sscanf */
            offset = 0,     /* offset in buf for next sscaf call */
            tmp = 0;        /* temp var to hold sscanf conversion */
        size_t len = strlen (buf);  /* length for line validation */
        /* validate whole line read into buf - exit on error */
        if (len == MAXC - 1 && buf[len - 1] != '\n') {
            fprintf (stderr, "error: line %d too long.\n", row + 1);
            return 1;
        }
        while (col < COLS &&    /* read each value in line into arr */
                sscanf (buf + offset, "%d%n", &tmp, &nchr) == 1) {
            arr[row][col++] = tmp;  /* assign tmp to array */
            offset += nchr;         /* update offset in buffer */
            idx[row]++;             /* update col-index for array */
        }
        row++;  /* increment row for next read */
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (int i = 0; i < row; i++) { /* output the arrays read */
        for (int j = 0; j < idx[i]; j++)
            printf (" %3d", arr[i][j]);
        putchar ('\n');
    }

    return 0;
}

Note: rather than using a fixed size 2D array, you can take things a step further and instead use a pointer-to-pointer-to-int (e.g. a double-pointer, int **arr;) and dynamically allocate and reallocate pointers for rows, as required, and dynamically allocate and reallocate the storage assigned to each pointer to handle any number of integer values per-row. While it is not that much additional work, that is left as an exercise to you when you get to dynamic allocation in your studies. What you are doing with an differing number of column values per-row is creating a jagged array.

Example Input File

Using your input file for testing, e.g.:

$ cat dat/3arr.txt
3 2 1 1 1
4 3 2
1 1 4 1

Example Use/Output

Produces the following output:

$ ./bin/arr_jagged dat/3arr.txt
   3   2   1   1   1
   4   3   2
   1   1   4   1

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85