0

I have this csv file contain people contact:

NAME, NUMBER, ADDRESS, EMAIL
Kevin Mahendra, +62 812-XXX-XXX, Jln. Anggrek Merah 3 No. 54, kevin@gmail.com
Adwi Lanang, +62 821-XXX-XXX, Jln. Ruhui Rahayu, adwi@gmail.com

and this is the code:

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

// Number of buckets for TABLE
#define N 26

#define MAX 50

typedef struct Contact
{
    char name[MAX];
    char number[MAX];
    char address[MAX];
    char email[MAX];
    struct Contact* next;
} 
Contact;

// Hash table
Contact *table[N];

int main(void)
{  
    // OPEN CSV FILE
    FILE *file = fopen("contacts.csv", "r");
    if (file == NULL)
    {
        printf("Error open file!\n");
        return 1;
    }
    
    while(!feof(file))
    {
        Contact *new = malloc(sizeof(Contact));
        fscanf(file, "%[^,],%[^,],%[^,],%[^,]", new->name, new->number, new->address, new->email);
        
        printf("%s,%s,%s,%s\n", new->name, new->number, new->address, new->email);

        int index = hash(new->name);
   
        new->next = table[index];
        table[index] = new; 
    }

}

As you can see, i try to put each person contact into hash table. But it fails to read csv file properly. Because when i try to printf it, the result is become infinite loop.

BUT it's working when my csv file only contain one line/row. It will printf the contact inside csv properly. But when i add a new contact/row into csv file, it fails to read.

How can i read csv file one line/row at a time? and maybe also skip the header NAME, NUMBER, ADDRESS, EMAIL? Thank you!

2 Answers2

0

The use of feof is wrong - see Why is “while ( !feof (file) )” always wrong?

Further, you should always check the value returned by fscanf to see that you have scanned the expected number of elements.

Try something like:

Contact *new = malloc(sizeof(Contact));   // Get a Contact for the first fscanf
if (new == NULL) exit(1);

while(fscanf(file, "%[^,],%[^,],%[^,],%[^,]", new->name, new->number, new->address, new->email) == 4)
{
    
    printf("%s,%s,%s,%s\n", new->name, new->number, new->address, new->email);

    int index = hash(new->name);

    new->next = table[index];
    table[index] = new; 

    Contact *new = malloc(sizeof(Contact));  // Get a Contact for the next fscanf
    if (new == NULL) exit(1);
}

free(new);  // Free the unused Contact
Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
0

The last field on each line is not succeeded by a comma, so you should look for a newline instead. Also to prevent overflow you must never read more than one character less than the buffer length. This can be achieved with a preprocessor trick:

#define STR_VALUE(x) STR(x)
#define STR(x) #x

#define MAX 49
#define MAX_STR STR_VALUE(MAX)

typedef struct Contact
{
    char name[MAX + 1];
    char number[MAX + 1];
    char address[MAX + 1];
    char email[MAX + 1];
    struct Contact* next;
}
Contact;
...
fscanf(file,
    "%" MAX_STR "[^,],%" MAX_STR "[^,],%" MAX_STR "[^,],%" MAX_STR "[^\n]",
    new->name, new->number, new->address, new->email);
...

In the loop you should stop when the result of fscanf (i.e. the number of scanned tokens) is any other value than four.

Edit 2021-04-06: Macro parameters are expanded before substitution except where they appear as operands of # (string representation) or ## (concatenation). Therefor

STR(MAX) => #MAX => "MAX"

whereas

STR_VALUE(MAX) => STR(49) => #49 => "49"
August Karlstrom
  • 10,773
  • 7
  • 38
  • 60
  • Thank you! This is working. But can you explain it what does this means `#define STR_VALUE(x) STR(x)` `#define STR(x) #x` `#define MAX_STR STR_VALUE(MAX)` ? – Kevinkun Official Apr 05 '21 at 17:45
  • Is there a way i can do to not include header in my csv `NAME, NUMBER, ADDRESS, EMAIL`? – Kevinkun Official Apr 05 '21 at 17:47
  • @KevinkunOfficial For the first question, see updated answer. For the second question, just remove the header from the CSV file, or skip character up to the first newline. – August Karlstrom Apr 06 '21 at 19:12