0

I have a program that takes user input and creates Records with several fields that the user enters. The user is asked to enter the first and last name of the person, address, city, state, zipcode, and phone #. I'm trying to sort the records in alphabetical order in relation to the city of each record. How would I go about this, I'm pretty stumped on how I could sort based on one variable and then print the entire record properly.

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

typedef struct Record Record;

struct Record
    {
        char fname[51];
        char lname[51];
        char address[51];
        char city[51];
        char state[51];
        char zipcode[51];
        char phoneNumber[51];

        Record *next;
    };


int main()
{
    FILE *fileWriter;
    const char filename[] = "data.txt";
    char answer = '\0';
    Record *records = NULL;
    Record *records_first = NULL;
    Record *records_previous = NULL;

    fileWriter = fopen(filename,"wt");

    if(fileWriter != NULL) {

        for( ; ; ) {
            records = (Record*) malloc(sizeof(Record));  

            if(records_first == NULL)
                records_first = records;

            if(records_previous != NULL)
                records_previous->next = records;

            records = records_first;
            printf("First Name: \n");
            scanf("%s", records->fname);
            fprintf(fileWriter,"%s\t",records->fname);

            printf("Last Name: \n");
            scanf("%s", records->lname);
            fprintf(fileWriter,"%s\t",records->lname);

            printf("Address: \n");
            scanf(" %[^\n]", records->address);
            fprintf(fileWriter,"%s\t",records->address);

            printf("City: \n");
            scanf("%s", records->city);
            fprintf(fileWriter,"%s\t",records->city);

            printf("State: \n");
            scanf("%s", records->state);
            fprintf(fileWriter,"%s\t",records->state);

            printf("Zipcode: \n");
            scanf("%s", records->zipcode);
            fprintf(fileWriter,"%s\t",records->zipcode);

            printf("Phone Number: \n");
            scanf("%s", records->phoneNumber);
            fprintf(fileWriter,"%s\t\n",records->phoneNumber);

            records->next = NULL;
            records_previous = records;

            printf("Are there anymore records? [y/n] ");
            scanf(" %c", &answer);

            if(tolower(answer) != 'y') {
                free(records);
                fclose(fileWriter);
                break;
            }
        }

    } else
        printf("Error opening file.");

    return 0;
}

                        **Edited Version**

I tried to use qsort(). but then ended up with a fatal error. On top of that, how would I print the entire record properly when I'm only able to sort one field?

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <ctype.h>

struct Record
    {
        char fname[51];
        char lname[51];
        char address[51];
        char city[51];
        char state[51];
        int zipcode;
        int phoneNumber;
    };

int compare_city(const void *const p1, const void *const p2)
 {
    struct Record *r1 = (struct Record *) p1;
    struct Record *r2 = (struct Record *) p1;

    return strcmp(r1->city, r2->city);
 }


int main()
{
    FILE *fileWriter;
    const char filename[] = "data.txt";
    char answer = 'y';
    int size = 1;
    int i = 0;
    int count = 1;
    struct Record *records = NULL;
    struct Record *records_temp = NULL;


    fileWriter = fopen(filename,"wb");
    if(fileWriter != NULL)
        while(answer == 'y' || answer == 'Y')
        {
            if(records_temp == NULL)
            {
                struct Record *records_temp = realloc(records,(size)*sizeof(*records));
            }

            records = records_temp;
            printf("First Name: \n");
            scanf("%s", records[i].fname);


            printf("Last Name: \n");
            scanf("%s", records[i].lname);


            printf("Address: \n");
            scanf(" %[^\n]", records[i].address);


            printf("City: \n");
            scanf("%s", records[i].city);


            printf("State: \n");
            scanf("%s", records[i].state);


            printf("Zipcode: \n");
            scanf("%s", records[i].zipcode);


            printf("Phone Number: \n");
            scanf("%s", records[i].phoneNumber);

            //stores all record info

            printf("Are there anymore records? [y/n] ");
            scanf(" %c", &answer);


        if(tolower(answer) == 'y')
        {
            i++;
            count++;
        }

        for(i = 0; i < count ; i++)
        {
            qsort(records, count, sizeof(struct Record), compare_city);
            fprintf(fileWriter,"%s\n",records[i].fname);
            fprintf(fileWriter,"%s\n",records[i].lname);
            fprintf(fileWriter,"%s\n",records[i].address);
            fprintf(fileWriter,"%s\n",records[i].city);
            fprintf(fileWriter,"%s\n",records[i].state);
            fprintf(fileWriter,"%d\n",records[i].zipcode);
            fprintf(fileWriter,"%d\n",records[i].phoneNumber);
        }

        free(records);
    }
    return 0;
}
Karlioh
  • 77
  • 1
  • 8
  • **Suggestion**: Use `scanf("%50s", records->fname);` to prevent buffer overflows. And you [don't need to cast `malloc()` return value](http://stackoverflow.com/a/605858/1983495), yet you must check that `malloc()` did not return `NULL`. – Iharob Al Asimi May 31 '15 at 01:16

2 Answers2

2

As is, you have a list structure, which cannot be sorted with qsort(), but you can use an array instead, and allocate it dynamically by using malloc() + realloc().

To sort the entries you can use qsort(), for example suppose you want to sort by the first name, then

int compare_first_name(const void *const p1, const void *const p2)
 {
    struct Record *r1 = (struct Record *) p1;
    struct Record *r2 = (struct Record *) p1;

    return strcmp(r1->fname, r2->fname);
 }

And then

qsort(base, count, sizeof(struct Record), compare_first_name);
Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • qsort on a linked list?? – A.S.H May 31 '15 at 01:35
  • @A.S.H after I started writing the answer, I went back to see how the elements of the _array_ were counted, and suddenly I noticed that it was a linked list, then ... And I said to my self, Oh no, I already finished writing the answer, let me post it anyway, although may be I will delete it, it depends on what the OP thinks about the suggestion to change it to an array. – Iharob Al Asimi May 31 '15 at 01:41
  • I originally tried using an array but ran into problems and kept getting access violations, someone suggested using a linked list to make printing several records on the same file easier. – Karlioh May 31 '15 at 01:45
  • @iharob yeah, I guessed thatt you missed that it was a linked list. ;). May be the solution should be to put the records into an array, but then a large and then check the count to not exceed the array size. afterwards, your solution is the perfect answer – A.S.H May 31 '15 at 01:45
  • @Kariloh start by creating an array for all your records and keep a count to not exceed its size (limit the user's entries to some count). Then use Iharob's solution – A.S.H May 31 '15 at 01:47
  • @A.S.H Instructions say specifically the user is allowed to enter as many records as they please, so the size of the array must be able to add one index if the user continues to say 'y' to the final condition – Karlioh May 31 '15 at 01:51
  • BTW, memory is limited anyway ;) – A.S.H May 31 '15 at 01:54
  • Ok I will try to recreate this with an array, brb – Karlioh May 31 '15 at 01:56
  • Better solution - write a simple `insert_sorted` function that inserts the linked-list records in sorted order, then just create a copy of the list by iterating over your existing list calling `insert_sorted` for each record thereby producing a sorted copy of your list. You can sort on any member of your struct. – David C. Rankin May 31 '15 at 22:57
  • @David C. Rankin How would I do that? – Karlioh May 31 '15 at 23:04
  • Give me a bit, this list is in rough shape, but I've hammered out most of the wrinkles. You always need to separate **input** and **saving**, you can get yourself in a real pickle overwriting your data that way `:p` I'll post a full solution in a bit. – David C. Rankin Jun 01 '15 at 04:55
2

As with most list "additions", there generally are few quick explanations of how to do what in many cases will be unique to your list implementation.

Before we look at reading the list data in sorted order (by city in your question), lets look at some basics that will help in doing anything with your list. First, and as is almost always the case, separate the input from save/output routines. In your case you are opening the file for saving in "wt" mode. Meaning each time you call your input function, you wipe out all existing data in the file and only write the new values you read. While this is fine for your one-shot input in main it is hardly realistic.

Further, when handling lists, your code with grow fairly lengthy fairly quickly. Trying to do this all in main will drive you batty. This necessarily means creating a function to handle the different list operations you will need. (this will help make your code much more manageable and readable. Moving your existing code to something like insert_records to handle gathering input from stdin will do. An example of your input handling moved to a function and with the file output trimmed looks like:

size_t insert_records (Record **records)
{
    // FILE *fileWriter;
    // const char filename[] = "dat/lldata.txt";
    char answer = 0;
    // Record *records = NULL;
    // Record *records_first = NULL;
    // Record *records_previous = NULL;
    Record *iter = NULL;
    size_t cnt = 0;
/*
    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fileWriter = fopen (*filename, "at"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        return 0;
    }
*/
    if (*records) {
        iter = *records;
        while (iter->next) iter = iter->next;
    }

    for (;;) 
    {
        Record *rec = malloc (sizeof *rec);     /* use malloc correctly */

        if (!rec) {
            fprintf (stderr, "%s() error: memory exhausted.\n", __func__);
            return 0;
        }

        printf ("\n  First Name: ");
        scanf (" %[^\n]%*c", rec->fname);    /* fix scanf format strings */
        // fprintf (fileWriter, "%s\t", rec->fname);

        printf ("   Last Name: ");
        scanf ("%[^\n]%*c", rec->lname);
        // fprintf (fileWriter, "%s\t", rec->lname);

        printf ("     Address: ");
        scanf ("%[^\n]%*c", rec->address);
        // fprintf (fileWriter, "%s\t", rec->address);

        printf ("        City: ");
        scanf ("%[^\n]%*c", rec->city);
        // fprintf (fileWriter, "%s\t", rec->city);

        printf ("       State: ");
        scanf ("%[^\n]%*c", rec->state);
        // fprintf (fileWriter, "%s\t", rec->state);

        printf ("     Zipcode: ");
        scanf ("%[^\n]%*c", rec->zipcode);
        // fprintf (fileWriter, "%s\t", rec->zipcode);

        printf ("Phone Number: ");
        scanf ("%[^\n]%*c", rec->phoneNumber);
        // fprintf (fileWriter, "%s\t\n", rec->phoneNumber);

        rec->next = NULL;

        if (!*records) {
            iter = *records = rec;
        } else {
            iter->next = rec;
            iter = iter->next;
        }

        cnt++;

        printf ("\nEnter additional records? [y/n] ");
        scanf (" %c%*c", &answer);

        if (answer == 'n' || answer == 'N') { /* why include ctype.h for this? */
            // free (records);
            // fclose (fileWriter);
            break;
        }
    }

    return cnt;
}

Note the return type was declared as size_t so you can return the number of records successfully entered (it can never be a negative number, so int isn't your best choice). Also Note the function takes the address of your list pointer (i.e. Record **records) as the argument which is required any time you may alter the first (starting) node in the list (unless you use a separate dummy pointer as the first node with a distinct address).

To save the list to a file, a separate routine provides greater protection against accidental data overwrite. A small save_list function is all that is needed. (note the filename pointer address is passed in a similar manner as records above so it can be changed and updated in the function itself.

size_t save_list (Record *rec, char **filename)
{
    if (!rec) {
        fprintf (stderr, "%s() error: list is empty.\n", __func__);
        return 0;
    }

    FILE *fp = NULL;
    Record *iter = rec;
    size_t cnt = 0;

    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fp = fopen (*filename, "wt"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        return 0;
    }

    for (; iter; iter = (iter->next ? iter->next : NULL))
    {
        fprintf (fp, "%s", iter->fname);
        fprintf (fp, "\t%s", iter->lname);
        fprintf (fp, "\t%s", iter->address);
        fprintf (fp, "\t%s", iter->city);
        fprintf (fp, "\t%s", iter->state);
        fprintf (fp, "\t%s", iter->zipcode);
        fprintf (fp, "\t%s\n", iter->phoneNumber);

        cnt++;
    }

    fclose (fp);

    return cnt;
}

Saving data to a file is pretty much useless unless you can actually read it back into your program. Since we will be sorting based on reading data into a second copy of the list, a sample input routine to read the data back into your program could be:

size_t read_records (Record **records, char **filename)
{
    FILE *fp = NULL;
    Record *iter = NULL;
    size_t cnt = 0;

    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fp = fopen (*filename, "r"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        if (*filename) free (*filename);    /* prevent returning invalid name */
        *filename = NULL;
        return 0;
    }

    if (*records) {
        iter = *records;
        while (iter->next) iter = iter->next;
    }

    for (;;)
    {
        Record *rec = malloc (sizeof *rec);
        if (!rec) {
            fprintf (stderr, "%s() error: memory exhausted.\n", __func__);
            return 0;
        }

        if (fscanf (fp, " %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\n]%*c",
                rec->fname, rec->lname, rec->address, rec->city, rec->state,
                rec->zipcode, rec->phoneNumber) != 7)
        {
            free (rec);
            break;
        }

        rec->next = NULL;

        if (!*records) {
            iter = *records = rec;
        } else {
            iter->next = rec;
            iter = iter->next;
        }

        cnt++;
    }

    fclose (fp);

    return cnt;
}

After you have your data read into the list, it would be helpful to have some way to look at it as well:

void prn_records (Record *records)
{
    if (!records) return;

    Record *iter = records;
    size_t cnt = 0;

    while (iter) {
        printf ("\n    record[%3zu]:\n\n", cnt);
        printf ("\t%s, %s\n", iter->lname, iter->fname);
        printf ("\t%s\n", iter->address);
        printf ("\t%s, %s %s\n", iter->city, iter->state, iter->zipcode);
        printf ("\t%s\n", iter->phoneNumber);
        cnt++;
        iter = iter->next;
    }
}

Now we can turn to the crux of you question. How to sort the list data by city?. As you have found out, there is no way to qsort a linked list, and while you can turn the list into an array of structs and then qsort the array of structs, it is just as easy to read the data into a second copy of the list in sorted order. Which is basically nothing more than modifying your read_records function, to insert the records in alphabetical order based on the city. There are more elegant ways to pass a pointer to a generic read function to allow sorting on any member, but for purposes here, a separate function to sort on city will suffice:

size_t read_city_sorted (Record **records, char **filename)
{
    FILE *fp = NULL;
    Record *iter = NULL;
    size_t cnt = 0;
    size_t inserted = 0;

    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fp = fopen (*filename, "r"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        if (*filename) free (*filename);    /* prevent returning invalid name */
        *filename = NULL;
        return 0;
    }

    for (;;)
    {
        inserted = 0;
        Record *rec = malloc (sizeof *rec);
        if (!rec) {
            fprintf (stderr, "%s() error: memory exhausted.\n", __func__);
            return 0;
        }
        rec->next = NULL;

        if (fscanf (fp, " %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\n]%*c",
                rec->fname, rec->lname, rec->address, rec->city, rec->state,
                rec->zipcode, rec->phoneNumber) != 7)
        {
            free (rec);
            break;
        }

        if (!*records) {    /* if no records insert as first */
            *records = rec;
        } else 
        {
            iter = *records;
            /* use strcmp to find location of city in sorted list */
            while ((strcmp (iter->city, rec->city) < 0) && iter->next) 
            {   
                /* check if alphetical order between iter & iter->next */
                if (strcmp (rec->city, iter->next->city) < 0) 
                {
                    rec->next = iter->next; /* insert in order      */
                    iter->next = rec;
                    inserted = 1;           /* set inserted flag    */
                    break;
                }

                iter = iter->next;
            }

            if (!inserted) {
                if (iter == *records) {     /* insert at beginning  */
                    rec->next = *records;
                    *records = rec;
                }
                else {                      /* insert at end        */
                    iter->next = rec;
                }
            }
        }

        cnt++;
    }

    fclose (fp);

    return cnt;
}

Now the frustrating part of list operations is there are many small details that go into putting the pieces of the puzzle together that without some working example, the functions above are just that, functions. To help explain, I put together a small driver program to tie the pieces together (which ended up much, much longer than originally planned -- due to the necessary little pieces). The code is commented and there are a few notes at the end. Digest it and let me know if you have any questions.

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

typedef struct Record Record;

struct Record {             /* static arrays are fine, but inefficient  */
    char fname[51];
    char lname[51];
    char address[51];
    char city[51];
    char state[51];
    char zipcode[51];
    char phoneNumber[51];   /* avoid mixed caPs (camelCase) in C */

    Record *next;           /* avoid initial Caps (not a hanging offence) */
};

size_t insert_records (Record **records);
size_t read_records (Record **records, char **filename);
size_t read_city_sorted (Record **records, char **filename);
size_t save_list (Record *rec, char **filename);
void prn_records (Record *records);
void free_records (Record *rec);

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

    Record *records = NULL; /* list pointer for linked-list */
    Record *sorted = NULL;  /* list pointer for sorted linked-list */
    char *datafile = argc > 1 ? strdup (argv[1]) : NULL;
    char c = 0;
    size_t numrec = 0;      /* number of records in list */
    size_t sortrec = 0;     /* number of records in sorted list     */

    char *fname = NULL;     /* allow save in new filename   */
    char *sfname = NULL;    /* save sorted list in separate filename */

    if (datafile)       /* if filename given on command line, read */
        numrec = read_records (&records, &datafile);

    for (;;)
    {   /* quick menu for list operations */
        printf ("\nSelect operation from list, 'q' when done:\n\n");
        printf ("\t1)  Insert Records Manually\n");
        printf ("\t2)  Read Records from File\n");
        printf ("\t3)  Read/Print Records from File (sorted on city)\n");
        printf ("\t4)  Show Number of Records in list\n");
        printf ("\t5)  Show Number of Records (sorted list)\n");
        printf ("\t6)  Print Records\n");
        printf ("\t7)  Print Sorted Records (on city)\n");
        printf ("\t8)  Save Records to File\n");
        printf ("\t9)  Save (sorted ) Records to File\n");
        printf ("\tq)  Quit\n");
        printf ("\n    selection: ");
        scanf (" %c%*c", &c);

        if (c == 'q' || c == 'Q') break;

        switch (c)
        {
            case '1' : numrec += insert_records (&records);
                break;
            case '2' : numrec += read_records (&records, &datafile);
                break;
            case '3' : sortrec = read_city_sorted (&sorted, &datafile);
                break;
            case '4' : printf ("\n  The list contains '%zu' records\n", numrec);
                break;
            case '5' : printf ("\n  The (sorted list) contains '%zu' records\n", sortrec);
                break;
            case '6' : prn_records (records);
                break;
            case '7' : prn_records (sorted);
                break;
            case '8' : save_list (records, &fname);
                break;
            case '9' : save_list (sorted, &sfname);
                break;
            default  : printf ("\n error: invalid selection\n");
                break;
        }
    }

    if (sorted) free_records (sorted);  /* no forced save of sorted, up to you  */

    if (records) {
        save_list (records, &fname);    /* force save before free, save in new  */
        free_records (records);         /* fname to keep original datafile      */
    }
    if (fname) free (fname);
    if (sfname) free (sfname);
    if (datafile) free (datafile);

    return 0;
}

size_t read_records (Record **records, char **filename)
{
    FILE *fp = NULL;
    Record *iter = NULL;
    size_t cnt = 0;

    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fp = fopen (*filename, "r"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        if (*filename) free (*filename);    /* prevent returning invalid name */
        *filename = NULL;
        return 0;
    }

    if (*records) {
        iter = *records;
        while (iter->next) iter = iter->next;
    }

    for (;;)
    {
        Record *rec = malloc (sizeof *rec);
        if (!rec) {
            fprintf (stderr, "%s() error: memory exhausted.\n", __func__);
            return 0;
        }

        if (fscanf (fp, " %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\n]%*c",
                rec->fname, rec->lname, rec->address, rec->city, rec->state,
                rec->zipcode, rec->phoneNumber) != 7)
        {
            free (rec);
            break;
        }

        rec->next = NULL;

        if (!*records) {
            iter = *records = rec;
        } else {
            iter->next = rec;
            iter = iter->next;
        }

        cnt++;
    }

    fclose (fp);

    return cnt;
}

size_t read_city_sorted (Record **records, char **filename)
{
    FILE *fp = NULL;
    Record *iter = NULL;
    size_t cnt = 0;
    size_t inserted = 0;

    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fp = fopen (*filename, "r"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        if (*filename) free (*filename);    /* prevent returning invalid name */
        *filename = NULL;
        return 0;
    }

    for (;;)
    {
        inserted = 0;
        Record *rec = malloc (sizeof *rec);
        if (!rec) {
            fprintf (stderr, "%s() error: memory exhausted.\n", __func__);
            return 0;
        }
        rec->next = NULL;

        if (fscanf (fp, " %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\t] %[^\n]%*c",
                rec->fname, rec->lname, rec->address, rec->city, rec->state,
                rec->zipcode, rec->phoneNumber) != 7)
        {
            free (rec);
            break;
        }

        if (!*records) {    /* if no records insert as first */
            *records = rec;
        } else 
        {
            iter = *records;
            /* use strcmp to find location of city in sorted list */
            while ((strcmp (iter->city, rec->city) < 0) && iter->next) 
            {   
                /* check if alphetical order between iter & iter->next */
                if (strcmp (rec->city, iter->next->city) < 0) 
                {
                    rec->next = iter->next; /* insert in order      */
                    iter->next = rec;
                    inserted = 1;           /* set inserted flag    */
                    break;
                }

                iter = iter->next;
            }

            if (!inserted) {
                if (iter == *records) {     /* insert at beginning  */
                    rec->next = *records;
                    *records = rec;
                }
                else {                      /* insert at end        */
                    iter->next = rec;
                }
            }
        }

        cnt++;
    }

    fclose (fp);

    return cnt;
}

size_t insert_records (Record **records)
{
    // FILE *fileWriter;
    // const char filename[] = "dat/lldata.txt";
    char answer = 0;
    // Record *records = NULL;
    // Record *records_first = NULL;
    // Record *records_previous = NULL;
    Record *iter = NULL;
    size_t cnt = 0;
/*
    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fileWriter = fopen (*filename, "at"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        return 0;
    }
*/
    if (*records) {
        iter = *records;
        while (iter->next) iter = iter->next;
    }

    for (;;) 
    {
        Record *rec = malloc (sizeof *rec);     /* use malloc correctly */

        if (!rec) {
            fprintf (stderr, "%s() error: memory exhausted.\n", __func__);
            return 0;
        }

        printf ("\n  First Name: ");
        scanf (" %[^\n]%*c", rec->fname);    /* fix scanf format strings */
        // fprintf (fileWriter, "%s\t", rec->fname);

        printf ("   Last Name: ");
        scanf ("%[^\n]%*c", rec->lname);
        // fprintf (fileWriter, "%s\t", rec->lname);

        printf ("     Address: ");
        scanf ("%[^\n]%*c", rec->address);
        // fprintf (fileWriter, "%s\t", rec->address);

        printf ("        City: ");
        scanf ("%[^\n]%*c", rec->city);
        // fprintf (fileWriter, "%s\t", rec->city);

        printf ("       State: ");
        scanf ("%[^\n]%*c", rec->state);
        // fprintf (fileWriter, "%s\t", rec->state);

        printf ("     Zipcode: ");
        scanf ("%[^\n]%*c", rec->zipcode);
        // fprintf (fileWriter, "%s\t", rec->zipcode);

        printf ("Phone Number: ");
        scanf ("%[^\n]%*c", rec->phoneNumber);
        // fprintf (fileWriter, "%s\t\n", rec->phoneNumber);

        rec->next = NULL;

        if (!*records) {
            iter = *records = rec;
        } else {
            iter->next = rec;
            iter = iter->next;
        }

        cnt++;

        printf ("\nEnter additional records? [y/n] ");
        scanf (" %c%*c", &answer);

        if (answer == 'n' || answer == 'N') { /* why include ctype.h for this? */
            // free (records);
            // fclose (fileWriter);
            break;
        }
    }

    return cnt;
}

void prn_records (Record *records)
{
    if (!records) return;

    Record *iter = records;
    size_t cnt = 0;

    while (iter) {
        printf ("\n    record[%3zu]:\n\n", cnt);
        printf ("\t%s, %s\n", iter->lname, iter->fname);
        printf ("\t%s\n", iter->address);
        printf ("\t%s, %s %s\n", iter->city, iter->state, iter->zipcode);
        printf ("\t%s\n", iter->phoneNumber);
        cnt++;
        iter = iter->next;
    }
}

size_t save_list (Record *rec, char **filename)
{
    if (!rec) {
        fprintf (stderr, "%s() error: list is empty.\n", __func__);
        return 0;
    }

    FILE *fp = NULL;
    Record *iter = rec;
    size_t cnt = 0;

    if (!(*filename)) {
        printf ("\nEnter filename for list data: ");
        scanf (" %m[^\n]%*c", filename);
    }

    if (!(fp = fopen (*filename, "wt"))) {
        fprintf (stderr, "%s() error: invalid filename '%s' (file not found).\n", 
                __func__, *filename);
        return 0;
    }

    for (; iter; iter = (iter->next ? iter->next : NULL))
    {
        fprintf (fp, "%s", iter->fname);
        fprintf (fp, "\t%s", iter->lname);
        fprintf (fp, "\t%s", iter->address);
        fprintf (fp, "\t%s", iter->city);
        fprintf (fp, "\t%s", iter->state);
        fprintf (fp, "\t%s", iter->zipcode);
        fprintf (fp, "\t%s\n", iter->phoneNumber);

        cnt++;
    }

    fclose (fp);

    return cnt;
}

void free_records (Record *rec)
{
    if (!rec) {
        fprintf (stderr, "%s() error: list is empty.\n", __func__);
        return;
    }

    Record *iter = rec;
    Record *victim = NULL;

    while (iter) 
    {
        victim = iter;
        iter = iter->next;
        if (victim) free (victim);
    }
}

Example Use/Output

Note: the data file to read can be provided as an argument to the program, or you can choose 2) Read Records from File and input the filename. Also note the code forces a save of the original list data under a new filename on exit. This preserves the original datafile unchanged. (you will be promted to enter a new filename on exit)

$ ./bin/ll_ins_sort dat/lldata.txt

Select operation from list, 'q' when done:

        1)  Insert Records Manually
        2)  Read Records from File
        3)  Read/Print Records from File (sorted on city)
        4)  Show Number of Records in list
        5)  Show Number of Records (sorted list)
        6)  Print Records
        7)  Print Sorted Records (on city)
        8)  Save Records to File
        9)  Save (sorted ) Records to File
        q)  Quit

    selection: 3

<_snipped menu_>

    selection: 7

    record[  0]:

        James, George
        32 Jones Place
        Billings, Montana 30412
        901 992-2165

    record[  1]:

        Doe, Jane
        459 35th Street
        Bridge City, Colorado 78763
        303 534-6734

    record[  2]:

        Mayer, Alphred
        222 Two Lane
        Chemco, Texas 77722
        713 555-1212

    record[  3]:

        Jones, Jill
        4312 Meandering Way
        Dallas, Texas 75248
        214 789-5391

    record[  4]:

        Barnes, Bill
        227 North Street
        Moosejaw, Maine 10103
        312 832-2189

    record[  5]:

        Early, Robert
        13 Sunrise Ln.
        Sunset, California 80210
        505 555-1212

<_snipped menu_>

    selection: 9

Enter filename for list data: dat/lldatasort.txt

<_snipped menu_>

    selection: q

Enter filename for list data: dat/lldatanew.txt

Original Input (created using input_records)

$ cat dat/lldata.txt
Alphred Mayer   222 Two Lane    Chemco  Texas   77722   713 555-1212
George  James   32 Jones Place  Billings        Montana 30412   901 992-2165
Bill    Barnes  227 North Street        Moosejaw        Maine   10103   312 832-2189
Jane    Doe     459 35th Street Bridge City     Colorado        78763   303 534-6734
Jill    Jones   4312 Meandering Way     Dallas  Texas   75248   214 789-5391
Robert  Early   13 Sunrise Ln.  Sunset  California      80210   505 555-1212

Sorted Output File Created on Save of Sorted

$ cat dat/lldatasort.txt
George  James   32 Jones Place  Billings        Montana 30412   901 992-2165
Jane    Doe     459 35th Street Bridge City     Colorado        78763   303 534-6734
Alphred Mayer   222 Two Lane    Chemco  Texas   77722   713 555-1212
Jill    Jones   4312 Meandering Way     Dallas  Texas   75248   214 789-5391
Bill    Barnes  227 North Street        Moosejaw        Maine   10103   312 832-2189
Robert  Early   13 Sunrise Ln.  Sunset  California      80210   505 555-1212
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • **Note:** above, `scanf` is used to allocate filenames using the `%m..` conversion specifier. On older versions of `scanf` and windows, you will need to substitute `%a` for `%m` if you receive warnings or errors. – David C. Rankin Jun 01 '15 at 09:51