1
int main(){
    FILE *file;
    char line[100];
    char name[26],code[4],donator[10],shipment[10], quantity[10];
    int count = 0;

    file = fopen("donation.txt","r");

    if(!file){
        printf("File does not exist!");
        return 1;
    }

    while (!feof(file)){
        fgets(line,100,file);
        count++;
    }
    char *list[count][5];
    memset(list,0,sizeof(list));

    fseek(file,0,SEEK_SET);
    count=0;

    int count2=0;
    char dtm[sizeof(line)];
    while (!feof(file)){
        fgets(line,100,file);
        if (count>0){
            strcpy(dtm,line);
            printf("%s",dtm);
            count2=0;
            for(char  *p = strtok(dtm,"|");p ; p=strtok(NULL,"|")){
                printf("\n %d %s",count2,p);
                list[count-1][count2]=p;
                printf("\n%s",list1[count-1][count2]);
                count2++;
            }
        }
        count++;
    }
    for(int i =0; i<count-1 ;i++){
        for(int k=0;k<count2;k++)
            printf("\n%d %d %s",i,k,list[i][k]);
    }
    fclose(file);
    return 0;
}

.

Contactless Thermommeter | CT          | Japan        | 1               | 1                      
Hand Sanitizer           | HS          | USA          | 1               | 1                      
Face Mask                | FM          | China        | 1               | 1                      
Surgical Mask            | SM          | China        | 1               | 1                      
Oxygen Mask              | OM          | Saudi Arabia | 1               | 1                                               

for loop's expected output snippet:

0 0 Contactless Thermometer<br/>
0 1  CT<br/>
0 2  Japan<br/>
0 3  1<br/>
0 4  1<br/>
1 0 Hand Sanitizer<br/>
1 1  HS<br/>
1 2  USA<br/>
1 3  1<br/>
1 4  1<br/>

for loop's output snippet:

0 0 Oxygen Mask<br/>
0 1  OM<br/>
0 2  Saudi Arabia<br/>
0 3  1<br/>
0 4  1<br/>
1 0 Oxygen Mask<br/>
1 1  OM<br/>
1 2  Saudi Arabia<br/>
1 3  1<br/>
1 4  1<br/>

I just started C after learning Python in my pre-U and I am very grateful if anyone here can guide me on what went wrong with my code. In the file reading process, I used strtok to break down the lines in the txt file and store in list[i][k], as shown in How to store tokens(strtok) in a pointer on an array. It shows the intended value but in the next for loop, list[i][k] only showed the last set of values as the picture below.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256

2 Answers2

1

Ok, the code is a bit messy, you simply want to map your file in a 2D array.

There are several troubles :

    if (count>0){

Why ? You want to have every lines in your array, don't skip the first one.

            list[count-1][count2]=p;

Skip the -1. It has nothing to do there.

            list[count-1][count2]=p;

Yes, two problems in the same line.

You assign in your array a pointer on a string that will change. And pointer aliasing. strtok returns a pointer to the actual string. It does not reallocate memory.

The solution is simply to strdup your string, so it has a fresh new memory that is not going to be changed on the next loop cycle.

            list[count][count2] = strdup(p);

Don't forget to free that later. Don't forget to check if your strdup did not fail :)

Other notes : You have unused variables. The newline remains in the string, in the last token. You may want to remove that.

mmeisson
  • 623
  • 6
  • 22
  • " newline remains in the string, in the last token. You may want to remove that." --> or change `"|"` to `"|\n"`. – chux - Reinstate Monica Oct 29 '21 at 13:58
  • @mmeisson sry about that. there's a line of header in my text file but i didn't show it here to make it less messy,and i used several printf lines to check the values. I'll try your method. – Louis Chung Oct 29 '21 at 15:06
0

strtok() returns a pointer into the original array, this array will be overwritten by the next call to fgets() so you should allocate a copy of the string. You can use strdup() for that:

    list[count][count2] = strdup(p);

Also note that while (!feof(file)) is not a reliable way to test if another line is available from the file. You should just call fgets() and check the return value:

    while (fgets(line, 100, file)) { ...

Here is a modified version:

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

int main(){
    FILE *file;
    char line[100];
    char name[26], code[4], donator[10], shipment[10], quantity[10];
    int count = 0;

    file = fopen("donation.txt", "r");
    if (!file) {
        printf("File does not exist!");
        return 1;
    }

    /* count the number of lines */
    while (fgets(line, sizeof line, file)) {
        count++;
    }

    /* allocate the 2D array of tokens and the per line token counts */
    char *list[count][5];
    int listlen[count];

    /* restart parsing at the beginning of the file */
    fseek(file, 0, SEEK_SET);
    count = 0;

    while (fgets(line, sizeof line, file)) {
        count2 = 0;
        /* split the line on |, also stripping the trailing newline
         * note however that strtok() does not allow for empty tokens */
        for (char *p = strtok(line, "|\n"); count2 < 5 && p != NULL; p = strtok(NULL, "|\n")) {
            list[count][count2] = strdup(p);
            count2++;
        }
        listlen[count] = count2;
        count++;
    }
    for (int i = 0; i < count; i++) {
        for (int k = 0; k < listlen[count]; k++)
            printf("%d %d %s\n", i, k, list[i][k]);
    }
    fclose(file);
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • @chux - Reinstate Monica Do you mind explaining whether memset is a must? I tried declaring a VLA but failed so I added memset based on https://stackoverflow.com/questions/17332360/initializing-variable-length-array – Louis Chung Oct 29 '21 at 16:07
  • @LouisChung `memset(list,...)` not needed here in chqrlie's answer. Ask yourself why you think it is needed. – chux - Reinstate Monica Oct 29 '21 at 16:34
  • @LouisChung: VLAs cannot have an initializer... you can use `memset` to force initialization, but in this does not seem necessary nor a fully portable way to initialize an array of pointers to `NULL`. – chqrlie Oct 29 '21 at 16:49
  • @chqrlie if I want to set this code as a function, is there a way for me to pass the array of tokens back into the main logic? I wanted to try declaring a structure within this function due to list's nature but examples online declare their structures externally. – Louis Chung Nov 01 '21 at 07:07
  • To return the array to the caller, you must return both the pointer and the number of elements: you can either use a structure with both as members and pass a pointer to it to the parser function or allocate an array with one extra entry for a `NULL` pointer at the end and you can return a pointer to this array to the caller. – chqrlie Nov 01 '21 at 18:38