Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating an array from a text file to call each line individually

Tags:

arrays

c

struct

I've looked at multiple other similar questions but none of them worked with my code, and I gave up trying to look things up.

I'm trying to create a program that takes each line, which has a book title, from a file into a character array, because I need to call each book later, so book[1], book[2], etc. However, I cannot figure out how to create the array with my struct.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#define Num_Book 9

typedef struct book {
  char *book_name;
  size_t length;
  ssize_t title;
}BOOK;

int main(int argc, char* argv[])
{
  BOOK *Book;
  Book = (BOOK *) malloc (sizeof(BOOK));

  (*Book).book_name = NULL;
  (*Book).length = 0;
  char title_arr[Num_Book][50]; 
  //this is the array that I tried creating, 
  //but it kept giving me warnings when I tried to compile

//opening my file
FILE *f_books;
f_books = fopen("books.txt", "r");

if (f_books == NULL)
{
    printf("Cannot open file for reading.\n");
}
printf("Book list\n");

while (((*Book).title = getline(&(*Book).book_name, &(*Book).length, f_books)) != -1)
{
    printf("%s", (*Book).book_name);
}

If anyone has any ideas, it is greatly appreciated. Thank you!

like image 786
Wheeeeeecode Avatar asked Nov 08 '22 01:11

Wheeeeeecode


1 Answers

The simplest approach is to declare an array of structs in main(), using the Num_Book macro that you defined in the preprocessor directives. Since you are using getline(), you do not even need to manually allocate memory to store the strings; instead you can let the getline() function do the work. Note that getline() is not a Standard C function, but it is POSIX, and also a GNU extension. On some systems you may need to enable this function using a feature test macro, which is included in the code below.

To take advantage of the automatic memory allocation capability of getline(), you need to pass in a null pointer for the first argument, and the second argument should be a pointer to a size_t variable containing a value of zero.

In the loop that reads the data into the structs, temporary variables are used instead of directly assigning to the struct fields. This allows the current string to be inspected before the final assignment, so that empty lines are not stored as book records.

Remember that getline() keeps the \n character, so if the current line is non-empty, the terminating newline is replaced with a \0 terminator. Then the values held by the temporary variables are assigned to the appropriate fields of the current BOOK struct, temp_name and temp_length are reset to NULL and 0, and i is incremented.

You still need to free memory allocated by getline(), so this should be done before ending the program.

Note that in the original code, while you were checking to be certain that the file books.txt was successfully opened, you did not exit() here. This would lead to problems in the event that the file failed to open, when the program went on as if it had opened. You could handle the error differently; for example, it may be appropriate to instead ask the user for a different filename.

#define _POSIX_C_SOURCE 200809L

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

#define Num_Book  9

typedef struct book {
    char *book_name;
    size_t length;
    ssize_t title;
} BOOK;

int main(void)
{
    BOOK books[Num_Book];

    FILE *f_books;
    f_books = fopen("books.txt", "r");

    if (f_books == NULL)
    {
        fprintf(stderr, "Cannot open file for reading.\n");
        exit(EXIT_FAILURE);
    }

    printf("Book list\n");

    char *temp_name = NULL;
    size_t temp_length = 0;
    ssize_t temp_title;
    char *find;
    size_t i = 0;
    while ((temp_title = getline(&temp_name, &temp_length, f_books))
           != -1 && temp_name[0] != '\n') {

        /* Replace newline with '\0' */
        if ((find = strchr(temp_name, '\n')) != NULL) {
            *find = '\0';
        }

        books[i].book_name = temp_name;
        books[i].length = temp_length;
        books[i].title = temp_title;
        temp_name = NULL;
        temp_length = 0;

        printf("%s\n", books[i].book_name);
        ++i;
    }

    /* Still need to free allocated memory */
    for (size_t j = 0; j < i; j++) {
        free(books[j].book_name);
    }
    if (temp_name) {
        free(temp_name);
    }

    if (fclose(f_books) != 0) {
        fprintf(stderr, "Unable to close file\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

Program output:

Book list
The Sound and the Fury
So Long, and Thanks for All the Fish
Gargantua and Pantagruel
Foundation

If you need to dynamically allocate space for the books, you can modify the above code. Below is a version that does this, first by initializing a variable max_books to a reasonable starting value. Space is allocated and assigned to a pointer to BOOK, and when a new book is added the space is reallocated if necessary.

After all books have been added, the allocation can be trimmed to exact size. Note the use of identifiers instead of explicit types in the sizeof arguments. This is less error-prone and easier to maintain if types change in future iterations of the code. Also note that realloc() will return a null pointer in the event of an allocation error. Here, direct assignment to books would result in the loss of previously stored data, and a memory leak. For this reason, the address of the new allocation is first stored in temp; the value of temp is assigned to books only if it is not NULL.

The same allocations must be freed as before, but in addition, the dynamically allocated array must also be freed.

#define _POSIX_C_SOURCE 200809L

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

typedef struct book {
    char *book_name;
    size_t length;
    ssize_t title;
} BOOK;

int main(void)
{


    FILE *f_books;
    f_books = fopen("books.txt", "r");

    if (f_books == NULL)
    {
        fprintf(stderr, "Cannot open file for reading.\n");
        exit(EXIT_FAILURE);
    }

    char *temp_name = NULL;
    size_t temp_length = 0;
    ssize_t temp_title;
    char *find;
    size_t i = 0;
    BOOK *books;
    BOOK *temp;
    size_t max_books = 10;
    size_t num_books = 0;

    if ((books = malloc((sizeof *books) * max_books)) == NULL) {
        fprintf(stderr, "Unable to allocate books\n");
        exit(EXIT_FAILURE);
    }

    while ((temp_title = getline(&temp_name, &temp_length, f_books))
           != -1 && temp_name[0] != '\n') {

        ++num_books;

        /* Replace newline with '\0' */
        if ((find = strchr(temp_name, '\n')) != NULL) {
            *find = '\0';
        }

        /* Reallocate books if more space is needed */
        if (num_books == max_books) {
            max_books *= 2;
            if ((temp = realloc(books, (sizeof *books) * max_books)) == NULL) {
                fprintf(stderr, "Unable to reallocate books\n");
                exit(EXIT_FAILURE);
            }
            books = temp;
        }

        /* Store book data */
        books[i].book_name = temp_name;
        books[i].length = temp_length;
        books[i].title = temp_title;
        temp_name = NULL;
        temp_length = 0;

        ++i;
    }

    /* If you need books to be trimmed to minimum size */
    if ((temp = realloc(books, (sizeof *books) * num_books)) == NULL) {
        fprintf(stderr, "Unable to trim books allocation\n");
        exit(EXIT_FAILURE);
    }
    books = temp;

    /* Display list of books */
    printf("Book list\n");
    for (i = 0; i < num_books; i++) {
        printf("%s\n", books[i].book_name);
    }

    /* Still need to free allocated memory */
    for (i = 0; i < num_books; i++) {
        free(books[i].book_name);
    }
    free(books);
    if (temp_name) {
        free(temp_name);
    }

    if (fclose(f_books) != 0) {
        fprintf(stderr, "Unable to close file\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}
like image 149
ad absurdum Avatar answered Nov 15 '22 05:11

ad absurdum