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