C Board  

Go Back   C Board > Community Boards > A Brief History of Cprogramming.com

 
 
LinkBack Thread Tools Display Modes
Old 05-27-2002, 06:33 PM   #1
Just because
 
ygfperson's Avatar
 
Join Date: Jan 2002
Posts: 2,502
Contest Results - May 27, 2002

Sorry these are late... It's hard getting a contest organized and running and stuff...

I'll begin by posting our respective opinions. (nvoigt, prelude, and me). then comes the code, then comes my last word.


To a moderator, sticky this, please? For just a couple days. Also, unsticky the Sign Up thread.

Last edited by ygfperson; 05-27-2002 at 06:45 PM.
ygfperson is offline  
Old 05-27-2002, 06:34 PM   #2
Just because
 
ygfperson's Avatar
 
Join Date: Jan 2002
Posts: 2,502
Nvoigt's opinion:
Cicero:
Quote:
speed:

faster than the user anyway.

efficiency:

Surprisingly efficient for such a short program.
Without any unneccessary stuff, but also lacks some
parts.

elegance:

Logically flowing, yes. Easy to read, no. But maybe it's
my editor and it looks better in the original one.


portability:

Compiles with two warnings, that, if taken seriously, could
have stopped a bug from creeping in.


specialized criteria:

sorting algorithms:

No sorting.

interface:

Simple interface, easy to navigate.

flexibility:

Holds 100 records and will crash thereafter.

Variable and Function naming:

Okay naming, program is not exactly large enough to see a pattern.

Standard procedures:

Standard function calls. No other procedures like algorithms.

Comments:

No comments.

Program structure and modularity:

Somewhat modular, each interface option is a function.



Conclusion:

This program has some highlights and some silly mistakes.
It is very compact and still functional. It lacks save/load
options and sorting. It shows some understanding of principles,
for example the scanf format-string.

Hard to say, but I'd give it a 3.
-1 for the bug,
-0.5 for the 100 records
-0.5 for not sorting/not saving
Hammer:
Quote:
speed:

The program is faster than the user anyway.
Sorting algorithm is not the fastest when
changing sort order, but this is a personal
phonebook, not a nationwide address lookup.

efficiency:

Efficient, special positive mention for
delete flags and save-on-demand !


elegance:

Source code is broken up into bits that are
easy to understand, looks neat and is nice to follow.


portability:

Compiled with three negligable warnings on VC7.
I don't have a *nix system at hand, but I didn't
notice any specialized code.


specialized criteria:

sorting algorithms:

Sorting algorithm is nice for inserting new records
which probably is the main focus. It's a bit slow on
reordering large phonebooks, but hopefully people will
only have a rather small list of phonecontacts ( <100 )
anyway.

interface:

The interface could use some linebreaks so single lines
and paragraphs are easier to make out, but for an interface
using only a commandline, it's as good as it gets.

flexibility:

Data structures can handle any amount of records. Structures themselves are fixed as in any database, for easier access on disc. Field lengths are easily changeable by changing the defines on top and recompile.

Variable and Function naming:

Good naming, conventions for function naming are clear and helpful.

Standard procedures:

Standard list procedures, standard sorting procedure.

Comments:

Good comments, light reading.

Program structure and modularity:

Code is seperated into 4 modules and main.
To make it easier in large projects, each
module should have it's own header. This way,
you don't force a full recompile of the whole
program when one prototype changes.



Conclusion:

Also the above may seem a bit negative at times, it's only because I won't mention all those positive points. Just look at it yourself. It's a great project, well done and well documented. If we had a smaller scale, I would probably deduct some points for the things mentioned above, so it might come out a 97/100, but on a scale of 5 to 1 I think it's a full 5.
ygfperson is offline  
Old 05-27-2002, 06:35 PM   #3
Just because
 
ygfperson's Avatar
 
Join Date: Jan 2002
Posts: 2,502
Prelude's opinion:
Cicero:
Quote:
Efficiency: 3*
Elegance: 1*
Portability: 2*
Sorting: 0*
Interface: 3*
Flexibility: 1*


Efficiency Notes:
This program is efficient because it sacrifices needed features. A random
access array of structs with a fixed length is used and the rest of the
program is what I would consider bare bones. It stores the input in a list
and allows minimal processing on that input for one run of the program. The
data is not saved and cannot be updated, the four options for maintaining
the list are add, remove, search, and print all. But even so, the program is
efficient and I can't cut points in this category for lack of features. I
did cut several points for the searching routines though since they do not
have an exit case and will iterate to the very end every time, even if the
field being searched for is the first one.

Elegance Notes:
Just about everything in this program is unsafe, from the use of gets to the
complete lack of error checking and input validation. The program also
breaks when the user attempts to search for a record or remove a record. The
memory allocation is truly nasty and it isn't freed with code. There's also
a problem when adding to an index in the array that was previously deleted.
The program deletes data from the array by clearing it out and changing the
name field to "empty". In the add_a_number function the array is searched
for the first empty field whether it actually be empty or simply deleted. If
it finds a deleted field the new record is placed there, but once the data
is input the function doesn't return and moves on to the situation where the
"empty" field was not found and another record must be input.

Portability Notes:
I question the portability of this program mostly because it is broken.
While it doesn't use any terribly nonstandard constructs, it's a toss-up
whether or not the program will run correctly on any platform due to the
errors. The actual code is standard and will compile, but I cut several
points because it is almost guaranteed to break on any platform, thus making
it completely nonportable. Once the errors were fixed the program worked
correctly, but I can't judge based on my changes.

Sorting Notes:
This program doesn't sort at all.

Interface Notes:
The interface isn't bad, but it could use a few more options and further
explanation for usage. Perhaps a help option that explains everything to the
user.

Flexibility Notes:
There is no flexibility in the number of records allowed. One may consider
100 records to be sufficient, but any well written program will be able to
handle that one user who has 50000 phonebook entries, even if everyone else
has 20. This entry uses dynamic allocation for each record, the concept is
excellent but the implementation falls far short of what I would expect in
any real program. For the dynamic allocation of input I gave an extra point,
but took away all of the others for the rest of the program.

Extra Comments
--------------
Variable and Function naming:
The use of the 'total' variable for maintaining the number of current
records is wrong since the implementation reserves identifiers starting with
'to' in lower case.

Program Comments:
There are no comments.

Personal Comments
-----------------
This entry is not one that I would use for my phonebook. When the errors
were fixed the program ran correctly, but it still lacks both useful and
necessary features.


-Prelude
Hammer:
Quote:
Efficiency: 4*
Elegance: 3*
Portability: 3*
Sorting: 4*
Interface: 5
Flexibility: 3*


Efficiency:
I have a bit of a problem with the choice of a linked list
for the data structure since a large number of records would result in a
considerable slow down. I personally would have used either a binary tree or
hashed table to speed the program up with larger numbers of records. This is
perhaps the biggest flaw of the entry.

Elegance Note:
While the code itself is very well written and clean, I have to cut a few
points on elegance due to the massive number of valid Lint warnings (264
Total). With minor changes in code structure most of these warnings could be
silenced.

Portability Note:
The use of fread and fwrite minimizes portability across different machines
if the data file is created on another system. While this doesn't seem
likely, it is a potential problem which would require another program to
translate between systems or convert the data file to text.

Sorting Algorithm Note:
In my opinion the choice of algorithm is a wise one as long as the number of
items in the list doesn't exceed a great number such as 1 million. This
doesn't seem likely so the algorithm is well suited to the data.

Flexibility Note:
I found the size limit of the data fields in the Record structure very
restrictive. While most input will be within the ranges chosen, some may not
and dynamic allocation would have been both more flexible for the user
and space saving in the data file.

Extra Comments
--------------
Variable and Function naming:
#ifndef __PHONE__ - Identifiers with leading underscores are reserved by the
implementation.

Personal Comments
-----------------
All in all this is an excellent program and most of my concerns are
nitpicking on unlikely situations. The numberof user options gives me the
sense that the programmer was doing more than just meeting a set of
requirements and if there were a judging criteria for going the extra mile
this entry would get a 5. I'm very impressed, well done.


-Prelude
ygfperson is offline  
Old 05-27-2002, 06:37 PM   #4
Just because
 
ygfperson's Avatar
 
Join Date: Jan 2002
Posts: 2,502
ygfperson's opinion:

cicero's:
Quote:
It looks really simple and elegant. Unfortunately, it stalls at places where you have to type into a prompt. (other than in adding names and numbers to the list). It does the bare minimum: store, retrieve, delete. (And of course, exit.)

efficiency: very efficient. 5
elegance: mostly elegant: 4.
portability: portable. 5
sorting algorithms: none - 0
interface: bare minimum, yet it works... except for the crashing. 2
flexibility: little flexibility. Mostly everything is set into predefined blocks. While a normal person doesn't have a name or phone number that exceeds 100 characters, he/she may have 100+ names to enter into the database. However, this program is modular. Its simplicity and size make it easy to maintain. 3
other comments: this is sort of what I was looking for. Something short and to the point. However, this sacrifices much, like sorting, to achieve that.
hammer's:
Quote:
efficiency: this program isn't lean but it's still mean. However, it doesn't bogart resources, it uses them properly. 4
elegance: very elegant. The only improvement I could see was to use C++. (But obviously he couldn't do that.) 5
portability: portable. 5
sorting algorithms: It looks like he used a bubble sort. It trades speed for code simplicity. However, on a scale like this, speed doesn't matter. I guess he could have done better... but on a simple contest like this? He doesn't need to. And then there's the linked list problem Prelude talked about. 4
Interface: very nice. Nested menus and everything. 5
Flexibility: while there are still limits on the names and phone numbers, the number of names and phone numbers is unlimited. (theoretically). It is also very modular and can probably be maintained easily. 5
Other comments: he really went out of his way to think out every possible feature. I expected something less for a mere phone book. But I guess I can settle for it. The only annoying thing about this program I found was the address. 5 lines for an address? I guess somebody in Tuxemencalitaappilatioanaville, Mississippi might have use for that. But that's merely a small detail. It's excellent.
ygfperson is offline  
Old 05-27-2002, 06:38 PM   #5
Just because
 
ygfperson's Avatar
 
Join Date: Jan 2002
Posts: 2,502
cicero:
Code:
#include     <stdio.h>
#include     <stdlib.h>
#include     <string.h>
int total=0;
struct
    {
    char*name;
    char*number;
    }list[100];
void add_a_number()
    {
    int i;
    for(i=0;i<total;i++)
        {
        if(!strcmp(list[i].name,"empty"))
            {
            printf("Type a name:");
            gets((list[i].name=calloc(100,sizeof(char))));
            printf("Type a phone number:");
            gets((list[i].number=calloc(100,sizeof(char))));
            }
        }
    printf("Type a name:");
    gets((list[total].name=calloc(100,sizeof(char))));
    printf("Type a phone number:");
    gets((list[total].number=calloc(100,sizeof(char))));
    total++;
    }
void remove_a_number()
    {
    int i;
    char*find;
    printf("Type a name to remove:");
    gets(find);
    for(i=0;i<total;i++)
        {
        if(!strcmp(find,list[i].name))
            {
            strcpy(list[i].name,"empty");
            strcpy(list[i].number,"empty");
            }
        }
    }
void find_a_number()
    {
    int i;
    char*find;
    printf("Type a name to find:");
    gets(find);
    for(i=0;i<total;i++)
        {
        if(!strcmp(find,list[i].name))
            {
            printf("Item#%d\n",i);
            printf("------\n");
            printf("Name:%s\n",list[i].name);
            printf("Number:%s\n",list[i].number);
            printf("\n\n");
            }
        }
    }
void list_all_numbers()
    {
    int i;
    for(i=0;i<total;i++)
        {
        printf("Item#%d\n",i);
        printf("------\n");
        printf("Name:%s\n",list[i].name);
        printf("Number:%s\n",list[i].number);
        printf("\n\n");
        }
    }
int main()
    {
    char opt;
    while(1)
        {
        printf("\t\t\tGod's Phonebook\n");
        printf("\t\t\t---------------\n");
        printf("\t\t\t1<- Add a number\n");
        printf("\t\t\t2<- Remove a number\n");
        printf("\t\t\t3<- Find a number\n");
        printf("\t\t\t4<- List all numbers\n");
        printf("\t\t\t5<- Quit\n");
        scanf("%1[12345]%*c",&opt);
        switch(opt)
            {
            case'1':
                add_a_number();
                break;
            case'2':
                remove_a_number();
                break;
            case'3':
                find_a_number();
                break;
            case'4':
                list_all_numbers();
                break;
            case'5':
                exit(0);
                break;
            }
        }
    }
ygfperson is offline  
Old 05-27-2002, 06:42 PM   #6
Just because
 
ygfperson's Avatar
 
Join Date: Jan 2002
Posts: 2,502
Hammer's:
ioUtilities.c:
Code:
#include "main.h"

/*
 ===============================================================================
 	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
 	File:		ioUtilities.c
 	Contents:	io_GetInt
				io_EnterToContinue
				io_GetYesNo
				io_GetLine
				io_WriteAllRecordsToFile
				io_WriteRecordToFile
				io_LoadAppConfig
				io_SaveAppConfig
 ===============================================================================
*/

static FILE *fp;

/*
 ===============================================================================
 	Function: 	ioLoadPhoneBookFromFile
    Args: 		Pointer to the node list 
    Returns: 	Pointer to the node list.
    			NULL
    Purpose:	Loads the Record structs from the phone book file into the List
 ===============================================================================
*/
struct Node *io_LoadPhoneBookFromFile(struct Node *List)
{
	struct Record	rec, *recptr;
	struct Node		*tmpListPtr;

	if ((fp = fopen(STR_FILENAME, "rb")) == NULL)
	{	/* Failed to open database file */
		printf("ERROR: Unable to open phone book file: %s\n", STR_FILENAME);
		return(List);
	}

	if (List)
	{	/* List not empty, kill it before we start reloading */
		(void)li_Traverse(List, free);
		List = li_Destroy(List);
	}

	while (fread(&rec, sizeof(struct Record), 1, fp) == 1)
	{
		if ((recptr = malloc(sizeof(struct Record))) == NULL)
		{					/* Memory failure, don't do anymore */
			perror("malloc");
			break;
		}

		*recptr = rec;

		if ((tmpListPtr = li_Insert(List, recptr, rec_Compare)) == NULL)
		{
			printf("Error performing insert on list.  Item not addded.\n");
			free(recptr);	/* throw record away */
		}
		else
		{
			List = tmpListPtr;
			recptr->ID = gl_HighestID;
			recptr->Status = (unsigned char)0;
		}
	}

	(void)fclose(fp);
	gl_AppData.DataChanged = FALSE;
	return(List);
}

/*
 ===============================================================================
    Function: 	io_GetInt
    Args:		maximum number for user to choose
    Returns: 	Number chosen, or -1 if x or X entered (for menu use)
    Notes:		Assumes that 0 will never be a valid option, and will ask
    			the user to re-enter.
    Purpose:	Get an int from stdin (keyboard).
 ===============================================================================
 */
int io_GetInt(int Max)
{
	int		Input, len;
	char	buffer[BUFSIZ];

	for (;;)
	{
		if ((len = io_GetLine(buffer, BUFSIZ, stdin)) != 0)
		{
			if (len == 1 && (buffer[0] == 'x' || buffer[0] == 'X'))
			{	/* Allow for xX to be entered, and return -1 */
				Input = RC_BAD;
				break;
			}

			if ((Input = atoi(buffer)) != 0)
			{
				if (Input >= 1 && Input <= Max)
				{
					break;
				}
			}
		}

		printf("Invalid. Try again (1-%d, x to exit)>", Max);
	}

	return(Input);
}

/*
 ===============================================================================
    Function: 	io_EnterToContinue
    Args: 		None
    Returns: 	None
    Purpose:	Simulates the good old DOS PAUSE
 ===============================================================================
 */
void io_EnterToContinue(void)
{
	printf("---> Enter To Continue <---");
	FLUSH_INPUT;
}

/*
 ===============================================================================
    Function: 	io_GetYesNo
    Args: 		String containing the prompt to be displayed
    Returns: 	TRUE if yY entered, FALSE if nN entered
    Purpose:	Get a Y or N from stdin (keyboard)
 ===============================================================================
 */
bool_t io_GetYesNo(char *prompt)
{
	int		c;
	bool_t	rc;

	for (;;)
	{
		printf("%s", prompt);
		c = getchar();
		switch (c)
		{
		case 'y':
		case 'Y':
			rc = TRUE;
			break;
		case 'n':
		case 'N':
			rc = FALSE;
			break;
		default:
			if (c != '\n') FLUSH_INPUT;
			continue;
		}
		break;
	}

	if (c != '\n') FLUSH_INPUT;
	return(rc);
}

/*
 ===============================================================================
    Function: 	io_GetLine
    Args: 		buffer to place the text
    			length of the buffer
    			file pointer to read from.
    Returns: 	Chars read and placed into buffer
    Purpose:	Same as fgets() but does not keep the newline character
 ===============================================================================
 */
int io_GetLine(char *buffer, int maxlen, FILE *fp)
{
	int		len = 0, c;
	char	*ptr = buffer;
	char	*endptr = buffer + maxlen - 1;

	if (fp != NULL)
	{
		while ((c = fgetc(fp)) != EOF)
		{
			if (c == '\n') break;
			*ptr = c;
			ptr++;
			len++;
			if (ptr == endptr) break;
		}
	}

	*ptr = '\0';
	return(len);
}

/*
 ===============================================================================
    Function: 	io_WriteAllRecordsToFile
    Args: 		Pointer to list 
    Returns: 	Number of items written.
    Purpose:	Save all Record structs in the List to a disk file
 ===============================================================================
 */
int io_WriteAllRecordsToFile(struct Node *List)
{
	unsigned char	status = ST_DELETED;

	if ((fp = fopen(STR_FILENAME, "wb")) == NULL)
	{	/* Failed to open database file */
		printf("ERROR: Unable to open phone book file: %s\n", STR_FILENAME);
		return(0);
	}

	/* First delete dead records from the list, then write them to disk */
	gl_AppData.MainList = li_DeleteNodeAndData(gl_AppData.MainList, &status, rec_CompareStatus);
	(void)li_Traverse(List, io_WriteRecordToFile);

	(void)fclose(fp);

	gl_AppData.DataChanged = FALSE;
	return(gl_HighestID);
}

/*
 ===============================================================================
    Function: 	io_WriteRecordToFile
    Args: 		Pointer to record structure to be written.
    Returns: 	Nothing
    Purpose:	Sub-function to write a single Record struct to disk
 ===============================================================================
 */
void io_WriteRecordToFile(void *ptr)
{
	struct Record	*rec = ptr;

	if (rec->Status & ST_DELETED) return;	/* Don't save deleted records */

	if (fp == NULL)
	{
		fprintf(stderr, "ERROR: Attempting to write to unopened file\n");
		return;
	}

	if (fwrite(rec, sizeof(struct Record), 1, fp) != 1) perror("WriteRecordToFile");
}

/*
 ===============================================================================
    Function: 	io_LoadAppConfig
    Args: 		None
    Returns: 	Nothing
    Purpose:	Loads the appconfig structure from disk
 ===============================================================================
 */
void io_LoadAppConfig(void)
{
	FILE				*myfp;
	struct appconfig	tmpcfg;

	if ((myfp = fopen(STR_CFG_FILENAME, "rb")) == NULL) return;
	if (fread(&tmpcfg, sizeof(struct appconfig), 1, myfp) == 1) gl_AppCfg = tmpcfg;

	(void)fclose(myfp);
}

/*
 ===============================================================================
    Function: 	io_SaveAppConfig
    Args: 		None
    Returns: 	Nothing
    Purpose:	Saves the appconfig structure to disk
 ===============================================================================
 */
void io_SaveAppConfig(void)
{
	FILE	*myfp;

	if ((myfp = fopen(STR_CFG_FILENAME, "wb")) == NULL) return;

	if (fwrite(&gl_AppCfg, sizeof(struct appconfig), 1, myfp) != 1)
		perror (STR_CFG_FILENAME);

	(void)fclose(myfp);
}
ListUtilities.c
Code:
#include "main.h"

/*
 ===============================================================================
 	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
 	File:		ListUtilities.c
 	Contents:	li_Create
 				nd_Create
 				nd_Destroy
 				li_Traverse
 				li_Insert
 				li_Destroy
 				li_GetDataPtrByID
 				li_DeleteNodeAndData
 				li_Count
 ===============================================================================
*/

/*
 ===============================================================================
    Function: 	li_Create
    Args: 		None
    Returns: 	NULL pointer.
    Purpose:	Dummy function to start off a list.
 ===============================================================================
 */
struct Node *li_Create(void)
{
	return (NULL);
}
 
/*
 ===============================================================================
    Function: 	nd_Create
    Args: 		A void ptr to the data to be referenced (A record).
    Returns: 	Pointer to new node, ready for insertion into the tree.
    			NULL if malloc fails
    Purpose:	Creates a single node, containing a pointer to some data (Record)
 ===============================================================================
 */
struct Node *nd_Create(void *ptr, struct Node *NextNode)
{
	struct Node *newptr;

	if ((newptr = malloc(sizeof(struct Node))) == NULL)
	{
		perror("nd_create, malloc");
		return (NULL);
	}

	newptr->DataPtr = ptr;
	newptr->Next = NextNode;
	return(newptr);
}

/*
 ===============================================================================
    Function: 	nd_Destroy
    Args: 		A pointer to a node to be free()'d
    Returns: 	Nothing
    Purpose:	The data the node points to is not freed by the function, 
    			only the node itself is.
 ===============================================================================
 */
void nd_Destroy(struct Node *ptr)
{
	free(ptr);
}

/*
 ===============================================================================
    Function: 	li_Traverse
    Args: 		A pointer to the first node in the list
    			A pointer to a function used to process each node.
    Returns: 	Number of Nodes processed
    Purpose:	Traverse the list, sending each nodes data to a function
 ===============================================================================
 */
int li_Traverse(struct Node *List, FPTR_Action fptr_Action)
{
	struct Node *Current = List;
	int Count = 0;
	
	while (Current)
	{
		fptr_Action(Current->DataPtr);
		Count++;
		Current = Current->Next;
	}
	return (Count);
}
/*
 ===============================================================================
    Function: 	li_Insert
    Args: 		A pointer to the first node in the list
    			A pointer to the data to be referenced by the node
    			A pointer to a function to be used for comparison.
    Returns: 	Pointer to the first node in the list
    			NULL is nd_Create fails.
    Notes:		The caller should use caution to ensure the List pointer
    			is not lost if this function returns NULL.
    Purpose:	Inserts a new node into the list in the correct sorted order.
 ===============================================================================
 */
struct Node *li_Insert(struct Node *List, void *NewData, FPTR_Compare fptr_Compare)
{
	struct Node *newptr;
	struct Node *Current = List;
	struct Node *Previous = NULL;
	
	while (Current)
	{
		if (fptr_Compare(Current->DataPtr, NewData) > 0)
			break; /* Found the entry point */
		Previous = Current; 
		Current = Current->Next;
	}
	
	if ((newptr = nd_Create(NewData, Current)) == NULL)
	{	/* malloc failure in nd_Create */
		return (NULL);
	}
	
	if (Previous == NULL)
		List = newptr;
	else
		Previous->Next = newptr;
	
	gl_HighestID++;
	return (List);
}

/*
 ===============================================================================
    Function: 	li_Destroy
    Args: 		A pointer to the first node in the list
    Returns: 	Nothing
    Purpose:	This runs through the list, destroying nodes.  It does
    			not free the data that each node points to.
 ===============================================================================
 */
struct Node *li_Destroy(struct Node *List)
{
	struct Node *Current = List;
	struct Node *tmp;
	
	while (Current)
	{
		tmp = Current->Next;
		nd_Destroy(Current);
		Current = tmp;
	}
	gl_HighestID = 0;
	
	return (NULL);
}

/*
 ===============================================================================
    Function: 	li_GetDataPtrByID
    Args: 		A pointer to the first node in the list
    			An int for the ID field.
    Returns: 	Pointer to a Record struct, allowing direct access by the caller
    Purpose:	Gets the address of a Record structure, as *owned* by the list.
 ===============================================================================
*/
struct Record *li_GetDataPtrByID(struct Node *List, int ID)
{
	struct Node *Current = List;
	struct Record *rec;
	
	while (Current)
	{
		rec = Current->DataPtr;
		if (rec->ID == ID)
			break;
		Current = Current->Next;
	}
	return ((Current)?Current->DataPtr:NULL);
}

/*
 ===============================================================================
    Function: 	li_DeleteNodeAndData
    Args: 		A pointer to the first node in the list
    			A pointer to the data to be referenced by the node
    			A pointer to a function to be used for comparison.
    Returns: 	Pointer to the first node in the list
    			NULL is nd_Create fails.
    Notes:		The caller should use caution to ensure the List pointer
    			is not lost if this function returns NULL.
    Purpose:	As the function name says!
 ===============================================================================
 */
struct Node *li_DeleteNodeAndData(struct Node *List, void *CompareData, FPTR_CompareDelete fptr_Compare)
{
	struct Node *Current = List;
	struct Node *Previous = NULL;
	struct Node *tmp;
	
	while (Current)
	{
		if (fptr_Compare(Current->DataPtr, CompareData) > 0)
		{	/* Found a node to remove */
			if (Current == List) List = Current->Next;
			tmp = Current->Next;
			if (Previous)
			{
				Previous->Next = tmp;
			}
			free (Current->DataPtr);
			free (Current);
			Current = tmp;
		}
		else
		{	/* No match, move on */
			Previous = Current; 
			Current = Current->Next;
		}
	}
	
	return (List);
}

/*
 ===============================================================================
    Function: 	li_Count
    Args: 		A pointer to the first node in the list
    Returns: 	Number of Nodes in list
    Purpose:	Counts the nodes.
 ===============================================================================
 */
int li_Count(struct Node *List)
{
	struct Node *Current = List;
	int Count = 0;
	
	while (Current)
	{
		Count++;
		Current = Current->Next;
	}
	return (Count);
}

/*
 ===============================================================================
    Function: 	li_Sort
    Args: 		A pointer to the first node in the list
    			Function pointer used for comparisons
    Returns: 	None
    Purpose:	Sorts the nodes, using the given function.
 ===============================================================================
 */
void li_Sort(struct Node *List,  FPTR_Compare fptr_Compare)
{
	struct Node *Current, *Next;
	void *Temp;
	bool_t StillDoingSwaps = TRUE;
	
	while (StillDoingSwaps == TRUE)
	{
		StillDoingSwaps = FALSE;
		Current = List;
		while (Current)
		{
			if ((Next = Current->Next) != NULL)
			{
				if (fptr_Compare (Current->DataPtr, Next->DataPtr) > 0)
				{
					Temp = Current->DataPtr;
					Current->DataPtr = Next->DataPtr;
					Next->DataPtr = Temp;
					StillDoingSwaps = TRUE;
				}
			}
			Current = Next;
		}
	}
}
main.c
Code:
#include "main.h"

/*
 ===============================================================================
 	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
 	File:		main.c
 	Contents:	Global variables
 				main
 ===============================================================================
*/

/*
 ===============================================================================
 	The following are the only global variables
 ===============================================================================
*/
int 	gl_HighestID;
struct 	appdata gl_AppData;
struct 	appconfig gl_AppCfg;

/*
 ===============================================================================
    Function: 	main
    Args: 		From command line.  
    			- This can be empty.  In this case an interactive menu is loaded.
    			- or if present, it is taken to represent search criteria.  
    			In this case, the database is loaded, searched and the 
    			relevant records displayed, then the application terminates.
    Returns: 	An int of course! (Always 0)     Don't void your main!  
 ===============================================================================
 */
int main(int argc, char *argv[])
{	
	struct Node *tmpListPtr;
	int i;

	/*
	 * Load and setup app config
	 */
	memset (&gl_AppData, 0, sizeof(struct appdata));
	memset (&gl_AppCfg, 0, sizeof(struct appconfig));
	
	io_LoadAppConfig();
	
	if (gl_AppCfg.LinesPerDisplay <= 0 || gl_AppCfg.LinesPerDisplay > MAX_LINES_PER_PAGE)
		gl_AppCfg.LinesPerDisplay = DEF_LINES_PER_PAGE;
	
	/*
	 * Create and load the link list of Record structs
	 */
	gl_AppData.MainList = li_Create();	
	
	if ((tmpListPtr = io_LoadPhoneBookFromFile(gl_AppData.MainList)) == NULL)
		printf("No data loaded from phone book.\n");
	else gl_AppData.MainList = tmpListPtr;
	
	/*
	 * If we have command line args, use them as search criteria, then terminate
	 */
	if (argc > 1)
	{
		gl_AppData.SearchField = RECF_NAME;
		for (i = 1; i < argc; i++)
		{
			strncpy(gl_AppData.SearchText, argv[i], MAXL_NAME+1);
			(void)li_Traverse (gl_AppData.MainList, rec_SearchAndPrint);
		}
		(void)li_Traverse (gl_AppData.MainList, free);
		(void)li_Destroy (gl_AppData.MainList);
		return (0);
	}
	
	/*
	 * Start interactive session, go into menu display until the user exits
	 */
	printf ("---> Auto-Save is %s <---\n", (gl_AppCfg.AutoSave)?"On":"Off");
	ui_DisplayMenu(MainMenu);
	
	/*
	 * Check if we need to save changes, then destory the memory we alloc'd earlier
	 */
	if (gl_AppData.DataChanged)
	{
		ui_Save(NULL);
	}
	
	(void)li_Traverse (gl_AppData.MainList, free);
	
	(void)li_Destroy (gl_AppData.MainList);
	
	/*
	 * Finally save the app config for next time
	 */
	io_SaveAppConfig();
	
	return (0);
}
RecordUtilities.c
Code:
#include "main.h"

/*
 ===============================================================================
 	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
 	File:		RecordUtilities.c
 	Contents:	rec_Compare
				rec_CompareID
				rec_CompareStatus
				rec_PrintShort
				rec_PrintLong
				rec_SearchAndPrint
 ===============================================================================
*/

/*
 ===============================================================================
    Function: 	rec_Compare
    Args: 		2 pointers to Record struct's to be compared
    Returns: 	<0 if p1 < p2
    			>0 if p1 > p2
    			 0 if p1 = p2
    Purpose:	Compares 2 Record struct's by the element specified in gl_AppCfg.SortField
 ===============================================================================
 */
int rec_Compare(const struct Record *rec1, const struct Record *rec2)
{
	char Fields[2][MAXL_FIELD+1];
	int len[2], i, j;
	
	/*
	 * Set up strings for case insensative compare
	 */
	if (gl_AppCfg.SortField == RECF_ID)
		return ((rec1->ID > rec2->ID));

	strncpy (Fields[0], rec_GetFieldPointer(rec1, gl_AppCfg.SortField), MAXL_FIELD+1); 
	strncpy (Fields[1], rec_GetFieldPointer(rec2, gl_AppCfg.SortField), MAXL_FIELD+1);
	len[0] = strlen(Fields[0]); 
	len[1] = strlen(Fields[1]);

	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < len[i]; j++)
		{
			if (isupper(Fields[i][j])) Fields[i][j] = tolower(Fields[i][j]);
		}
	}
	
	return (strcmp(Fields[0], Fields[1]));
}

/*
 ===============================================================================
    Function: 	rec_CompareID
    Args: 		Pointer to Record struct's to be compared with ID
    Returns: 	>0 if match
    			 0 if no match
    Purpose:	Compares 2 Record structs by the ID element
 ===============================================================================
 */
int rec_CompareID(const struct Record *rec, void *ptr)
{
	int *idptr = ptr;
	
	if (rec->ID == *idptr)
		return (1);
	else return (0);
}

/*
 ===============================================================================
    Function: 	rec_CompareStatus
    Args: 		Pointer to Record struct's to be compared with Status field
    Returns: 	>0 if match
    			 0 if no match
    Purpose:	Compares 2 Record structs by the Status element
 ===============================================================================
 */
int rec_CompareStatus(const struct Record *rec, void *ptr)
{
	unsigned char *statusptr = ptr;
	
	if (rec->Status & *statusptr) /* bitwise comparison */
		return (1);
	else return (0);
}
/*
 ===============================================================================
    Function: 	rec_PrintShort
    Args: 		A pointer to a Record struct to be printed
    Returns: 	Nothing
    Purpose:	Print only a couple of fields from a single Record struct
 ===============================================================================
 */
void rec_PrintShort(void *ptr)
{
	struct Record *rec = ptr;
	printf("%s%3d> %-20s %s %s\n",(rec->Status & ST_DELETED)?"Deleted>":"",  rec->ID, rec->Name, rec->PhoneNum1, rec->EmailAddr);
}

/*
 ===============================================================================
    Function: 	rec_PrintLong
    Args: 		A pointer to a Record struct to be printed
    Returns: 	Nothing
    Purpose:	Prints all fields from a single Record struct
 ===============================================================================
 */
void rec_PrintLong(void *ptr)
{
	struct Record *rec = ptr;
	
	printf ("Record ID:\t%d %s\n", rec->ID, (rec->Status & ST_DELETED)?"(Deleted)":"");
	printf ("Name:\t\t%s\n", rec->Name);
	printf ("1st Phone:\t%s\n", rec->PhoneNum1);
	printf ("2nd Phone:\t%s\n", rec->PhoneNum2);
	printf ("3rd Phone:\t%s\n", rec->PhoneNum3);
	printf ("Address:\t%s\n", rec->AddrLine1);
	if (rec->AddrLine2[0] != '\0') printf ("        \t%s\n", rec->AddrLine2);
	if (rec->AddrLine3[0] != '\0') printf ("        \t%s\n", rec->AddrLine3);
	if (rec->AddrLine4[0] != '\0') printf ("        \t%s\n", rec->AddrLine4);
	if (rec->AddrLine5[0] != '\0') printf ("        \t%s\n", rec->AddrLine5);
	printf ("Email Addr:\t%s\n", rec->EmailAddr);
	printf ("Misc Data:\t%s\n", rec->Misc);
}

/*
 ===============================================================================
    Function: 	rec_SearchAndPrint
    Args: 		A pointer to a Record struct to be printed (if criteria meet)
    Returns: 	Nothing
    Purpose:	Called during the list traversal process, this function will
    			search a specified field for the search string stored in the
    			global struct gl_AppData.SearchText
 ===============================================================================
 */
void rec_SearchAndPrint(void *ptr)
{
	struct Record *rec = ptr;
	char *sptr, *tmp;
	int len, i;
	char rectext[MAXL_FIELD+1];
	
	if (gl_AppData.SearchField == RECF_DUMMY)
		sptr = NULL;
	else sptr = rec_GetFieldPointer(rec, gl_AppData.SearchField);
	
	if (sptr)
	{
		strncpy(rectext, sptr, MAXL_FIELD+1);

		len = strlen(rectext);
		/* Convert data to lower case to allow for case insensative search */
		for (i = 0, tmp = rectext; i < len; i++, tmp++)
			{if (isupper(*tmp)) *tmp = tolower(*tmp);}
		if (!strstr(rectext, gl_AppData.SearchText))
			return;
	}

	rec_PrintShort(ptr);
	
	gl_AppData.LinesDisplayedSoFar++;
	if (gl_AppData.LinesDisplayedSoFar == gl_AppCfg.LinesPerDisplay)
	{
		io_EnterToContinue();
		gl_AppData.LinesDisplayedSoFar = 0;
	}
}

/*
 ===============================================================================
    Function: 	rec_GetFieldPointer
    Args: 		A pointer to a Record struct, and a field type indicator
    Returns: 	A char pointer to the start of the field specified by the type indicator
    Purpose:	Quick way to obtain string from the Record struct.
 ===============================================================================
 */
char *rec_GetFieldPointer(const struct Record *rec, enum RECORD_FIELDS WhichField)
{
	char *sptr;
	switch (WhichField)
	{
		case RECF_NAME: sptr = (char *)rec->Name; break;
		case RECF_PHONENUM1: sptr = (char *)rec->PhoneNum1; break;
		case RECF_PHONENUM2: sptr = (char *)rec->PhoneNum2; break;
		case RECF_PHONENUM3: sptr = (char *)rec->PhoneNum3; break;
		case RECF_ADDRLINE1: sptr = (char *)rec->AddrLine1; break;
		case RECF_ADDRLINE2: sptr = (char *)rec->AddrLine2; break;
		case RECF_ADDRLINE3: sptr = (char *)rec->AddrLine3; break;
		case RECF_ADDRLINE4: sptr = (char *)rec->AddrLine4; break;
		case RECF_ADDRLINE5: sptr = (char *)rec->AddrLine5; break;
		case RECF_EMAILADDR: sptr = (char *)rec->EmailAddr; break;
		case RECF_MISC: sptr = (char *)rec->Misc; break;
		default: sptr = (char *)rec->Name; break;
	}
	return (sptr);
}
UserInterfaceUtilities.c
Code:
#include "main.h"

/*
 ===============================================================================
 	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
 	File:		UserInterfaceUtilities.c
 	Contents:	Menu structs
 				ui_DisplayMenu
				ui_AddNewEntry
				ui_UpdateEntry
				ui_AddOrUpdateEntry
				ui_DeleteEntry
				ui_UndeleteEntry
				ui_SearchAll
				ui_SearchByID
				ui_DisplayAll
				ui_Save
				ui_PurgeDeleted
				ui_ShowInfo
				ui_ToggleAutoSave
				ui_SetLinesPerDisplay
 ===============================================================================
*/

/*
 ===============================================================================
 	The following are the structs that make up the menus.
 ===============================================================================
*/

static struct menu_item	OptionsMenu[] = 
{
	{"Auto-Save Setup", ui_ToggleAutoSave, NULL },
	{"Set Lines Per Display", ui_SetLinesPerDisplay, NULL },
	{ NULL, NULL, NULL }
};

static struct menu_item	SearchByFieldMenu[] =
{
	{ "Search By ID", ui_SearchAll, (void *)RECF_ID },
	{ "Search By Name", ui_SearchAll, (void *)RECF_NAME },
	{ "Search By Phone Num 1", ui_SearchAll, (void *)RECF_PHONENUM1 },
	{ "Search By Phone Num 2", ui_SearchAll, (void *)RECF_PHONENUM2 },
	{ "Search By Phone Num 3", ui_SearchAll, (void *)RECF_PHONENUM3 },
	{ "Search By Phone Num 3", ui_SearchAll, (void *)RECF_PHONENUM3 },
	{ "Search By Address Line 1", ui_SearchAll, (void *)RECF_ADDRLINE1 },
	{ "Search By Address Line 2", ui_SearchAll, (void *)RECF_ADDRLINE2 },
	{ "Search By Address Line 3", ui_SearchAll, (void *)RECF_ADDRLINE3 },
	{ "Search By Address Line 4", ui_SearchAll, (void *)RECF_ADDRLINE4 },
	{ "Search By Address Line 5", ui_SearchAll, (void *)RECF_ADDRLINE5 },
	{ "Search By Email Address", ui_SearchAll, (void *)RECF_EMAILADDR },
	{ "Search By Misc Field", ui_SearchAll, (void *)RECF_MISC },
	{ NULL, NULL, NULL }
};

static struct menu_item	SortByFieldMenu[] =
{
	{ "Sort By ID", ui_SetSortBy, (void *)RECF_ID },
	{ "Sort By Name", ui_SetSortBy, (void *)RECF_NAME },
	{ "Sort By Phone Num 1", ui_SetSortBy, (void *)RECF_PHONENUM1 },
	{ "Sort By Phone Num 2", ui_SetSortBy, (void *)RECF_PHONENUM2 },
	{ "Sort By Phone Num 3", ui_SetSortBy, (void *)RECF_PHONENUM3 },
	{ "Sort By Phone Num 3", ui_SetSortBy, (void *)RECF_PHONENUM3 },
	{ "Sort By Address Line 1", ui_SetSortBy, (void *)RECF_ADDRLINE1 },
	{ "Sort By Address Line 2", ui_SetSortBy, (void *)RECF_ADDRLINE2 },
	{ "Sort By Address Line 3", ui_SetSortBy, (void *)RECF_ADDRLINE3 },
	{ "Sort By Address Line 4", ui_SetSortBy, (void *)RECF_ADDRLINE4 },
	{ "Sort By Address Line 5", ui_SetSortBy, (void *)RECF_ADDRLINE5 },
	{ "Sort By Email Address", ui_SetSortBy, (void *)RECF_EMAILADDR },
	{ "Sort By Misc Field", ui_SetSortBy, (void *)RECF_MISC },
	{ NULL, NULL, NULL }
};

static struct menu_item	Page2Menu[] =
{
	{ "Search By Chosen Field", ui_DisplayMenu, SearchByFieldMenu },
	{ "Display All Entries", ui_DisplayAll, NULL },
	{ "Set Sort Order", ui_DisplayMenu, SortByFieldMenu },
	{ "Undelete An Entry", ui_UndeleteEntry, NULL },
	{ "Save Changes", ui_Save, NULL },
	{ "Purge Entries Marked For Deletion", ui_PurgeDeleted, NULL },
	{ "Application Options", ui_DisplayMenu, OptionsMenu },
	{ NULL, NULL, NULL }
};

struct menu_item	MainMenu[] =
{
	{ "Add New Entry", ui_AddNewEntry, NULL },
	{ "Delete Entry", ui_DeleteEntry, NULL },
	{ "Update Entry", ui_UpdateEntry, NULL },
	{ "Display Entry", ui_SearchAll, (void *)RECF_ID},
	{ "Search By Name", ui_SearchAll, (void *)RECF_NAME },
	{ "Save Changes", ui_Save, NULL },
	{ "More Options", ui_DisplayMenu, Page2Menu },
	{ "Help/About", ui_ShowInfo, NULL },
	{ NULL, NULL, NULL }
};

/*
 ===============================================================================
 	Function: 	ui_DisplayMenu
    Args: 		Pointer to a menu to be displayed 
    Returns: 	Nothing
    Purpose:	Displays the menu, gets the users option and processes it.
 ===============================================================================
*/
void ui_DisplayMenu(void *pmenu)
{
	int					i, choice;
	struct menu_item *menu = pmenu;
	struct menu_item	*item;

	for (;;)
	{
		printf ("---> PhoneBook Menu <---\n");
		for (i = 1, item = menu; item->Text != NULL; item++, i++)
		{
			printf("%2d %s\n", i, item->Text);
		}

		printf(" x Exit Menu\nEnter choice >");

		if ((choice = io_GetInt(i-1)) == RC_BAD)
			break;
		item = menu + choice - 1;
		if (item->Text == NULL) break;
		(void) (*item->fptr) (item->args);
	}
}

/*
 ===============================================================================
 	Function: 	ui_AddNewEntry
    Args: 		None (NULL pointer)
    Returns: 	Nothing
    Purpose:	Caller function to process a new entry request. 
    			The menu is capable of calling the next function directly,
    			but I left this in to assist future developments.
 ===============================================================================
*/
void ui_AddNewEntry(void *Dummy)
{
	ui_AddOrUpdateEntry(Dummy);
}

/*
 ===============================================================================
 	Function: 	ui_UpdateEntry
    Args: 		None (NULL Pointer)
    Returns: 	Nothing
    Purpose:	Asks user for an ID, then calls another function to do record updates
 ===============================================================================
*/
void ui_UpdateEntry(void *Dummy)
{
	struct Record *rec;
	int choice;
	
	if (gl_HighestID == 0)
	{
		printf ("No entries in the database!\n");
		io_EnterToContinue();
		return;
	}
	
	for (;;)
	{
		printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
		if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
			return;
		if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
		{
			printf("No such entry: %d\n", choice);
		}
		else break;
	}
	
	ui_AddOrUpdateEntry(rec);
}

/*
 ===============================================================================
 	Function: 	ui_AddOrUpdateEntry
    Args: 		Pointer to a struct to be updated, OR NULL is request is to ADD.
    Returns: 	Nothing
    Purpose:	Obtains user data to complete a Record structure, then
    			adds or updates a record in the list.
 ===============================================================================
*/
void ui_AddOrUpdateEntry(struct Record *newrec)
{
	struct Record rec;
	char buffer[MAXL_FIELD+1];
	struct Node *tmpListPtr;
	
	memset (&rec, 0, sizeof(struct Record));
	/*
	 * If newrec is not NULL, the it must be pointing to a Record struct and
	 * therefore we are updating an existing record.  In this case, we show
	 * the user the existing data before asking for new.  If they press <enter>
	 * on a blank line, we leave the old data in place, this saves them retyping
	 * each field when they are only changing one.
	 */
	if (newrec != NULL) 
	{
		rec = *newrec;
		printf("(Press <Enter> on blank line to keep existing text)\nCurrent Name: %s\n", newrec->Name);
	}
	printf ("Enter Name (max %d chars) >", MAXL_NAME);
	if (io_GetLine(buffer, MAXL_NAME+1, stdin) == 0)
	{
		if (newrec == NULL)
			return; /* allow user to break out by entering a new record with no name */
	}
	else strncpy (rec.Name, buffer, MAXL_NAME+1);
	
	if (newrec != NULL) printf("Current 1st Phone: %s\n", newrec->PhoneNum1);
	printf("Enter 1st Phone (max %d chars) >", MAXL_PHONENUM);
	if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
		strncpy (rec.PhoneNum1, buffer, MAXL_PHONENUM+1);
	
	if (newrec != NULL) printf("Current 2nd Phone: %s\n", newrec->PhoneNum2);
	printf("Enter 2nd Phone (max %d chars) >", MAXL_PHONENUM);
	if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
		strncpy (rec.PhoneNum2, buffer, MAXL_PHONENUM+1);
		
	if (newrec != NULL) printf("Current 3rd Phone: %s\n", newrec->PhoneNum3);
	printf("Enter 3rd Phone (max %d chars) >", MAXL_PHONENUM);
	if (io_GetLine(buffer, MAXL_PHONENUM+1, stdin) > 0)
		strncpy (rec.PhoneNum3, buffer, MAXL_PHONENUM+1);
	
	if (newrec != NULL) printf("Current Address Line 1: %s\n", newrec->AddrLine1);
	printf("Enter Address Line 1 of 5 (max %d chars) >", MAXL_ADDR);
	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
		strncpy (rec.AddrLine1, buffer, MAXL_ADDR+1);

	if (newrec != NULL) printf("Current Address Line 2: %s\n", newrec->AddrLine2);
	printf("Enter Address Line 2 of 5 (max %d chars) >", MAXL_ADDR);
	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
		strncpy (rec.AddrLine2, buffer, MAXL_ADDR+1);
	
	if (newrec != NULL) printf("Current Address Line 3: %s\n", newrec->AddrLine3);
	printf("Enter Address Line 3 of 5 (max %d chars) >", MAXL_ADDR);
	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
		strncpy (rec.AddrLine3, buffer, MAXL_ADDR+1);
	
	if (newrec != NULL) printf("Current Address Line 4: %s\n", newrec->AddrLine4);
	printf("Enter Address Line 4 of 5 (max %d chars) >", MAXL_ADDR);
	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
		strncpy (rec.AddrLine4, buffer, MAXL_ADDR+1);
	
	if (newrec != NULL) printf("Current Address Line 5: %s\n", newrec->AddrLine5);
	printf("Enter Address Line 5 of 5 (max %d chars) >", MAXL_ADDR);
	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
		strncpy (rec.AddrLine5, buffer, MAXL_ADDR+1);
			
	if (newrec != NULL) printf("Current Email Address: %s\n", newrec->EmailAddr);
	printf("Enter Email Address (max %d chars) >", MAXL_ADDR);
	if (io_GetLine(buffer, MAXL_ADDR+1, stdin) > 0)
		strncpy (rec.EmailAddr, buffer, MAXL_ADDR+1);
	
	if (newrec != NULL) printf("Current Misc data: %s\n", newrec->Misc);
	printf("Enter Misc Details (max %d chars) >", MAXL_MISC);
	if (io_GetLine(buffer, MAXL_MISC+1, stdin) > 0)
		strncpy (rec.Misc, buffer, MAXL_MISC+1);
	
	if (newrec == NULL) {rec.ID = gl_HighestID+1; rec.Status = 0;};
	
	/*
	 * Display new/changed details for user to review
	 */
	printf ("Record Details Now:\n");
	rec_PrintLong(&rec);
	
	if (io_GetYesNo("Confirm Details (y/n) >") == TRUE)
	{	/* User has confirmed new details, so update/add record and add to list */
		if (newrec)	/* updating an existing record */
		{
			*newrec = rec;
		}
		else
		{	/* Adding a new record */
			if ((newrec = malloc(sizeof (struct Record))) == NULL)
			{
				perror ("Add/Update failed on malloc");
				return;
			}
			*newrec = rec;
			if ((tmpListPtr = li_Insert (gl_AppData.MainList, newrec, rec_Compare)) == NULL)
			{
				printf("Error performing insert on list.  Item not addded.\n");
				free(newrec);	/* throw the record away */
			}
			else 
			{	
				gl_AppData.MainList = tmpListPtr;
				newrec->ID = gl_HighestID;
			}
		}
		gl_AppData.DataChanged = TRUE;
		if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
	}
	
}

/*
 ===============================================================================
 	Function: 	ui_DeleteEntry
    Args: 		None (NULL pointer)
    Returns: 	Nothing
    Purpose:	Gets an ID from the user, displays the record then marks it
    			for deletion if the user agrees.  If autosave is on, the
    			list will be written to disk and the record will be erased
    			completely (undelete not available in this case).
 ===============================================================================
*/
void ui_DeleteEntry(void *Dummy)
{
	struct Record *rec;
	int choice;

	if (gl_HighestID == 0)
	{
		printf ("No entries in the database!\n");
		io_EnterToContinue();
		return;
	}
	
	printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
	if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
		return;
	if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
	{
		printf("No such entry: %d\n", choice);
		io_EnterToContinue();
		return;
	}
	
	rec_PrintLong(rec);
	
	if (io_GetYesNo("Confirm Delete (y/n) >") == TRUE)
	{
		rec->Status = rec->Status | ST_DELETED;
		gl_AppData.DataChanged = TRUE;
		if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
	}
}

/*
 ===============================================================================
 	Function: 	ui_UndeleteEntry
    Args: 		None (NULL Pointer)
    Returns: 	Nothing
    Purpose:	Gets an ID from the user, and unsets the deleted flag.
 ===============================================================================
*/
void ui_UndeleteEntry(void *Dummy)
{
	struct Record *rec;
	int choice;
	
	if (gl_HighestID == 0)
	{
		printf ("No entries in the database!\n");
		io_EnterToContinue();
		return;
	}
	
	printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
	if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
		return;
	if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
	{
		printf("No such entry: %d\n", choice);
		io_EnterToContinue();
		return;
	}
	
	if (rec->Status & ST_DELETED)
	{
		rec->Status = rec->Status ^ ST_DELETED;
		printf ("Record undeleted\n");
		gl_AppData.DataChanged = TRUE;
		if (gl_AppCfg.AutoSave) (void)io_WriteAllRecordsToFile(gl_AppData.MainList);
	}
	else
		printf ("Record was not marked for deletion\n");
	
	io_EnterToContinue();
	
}

/*
 ===============================================================================
 	Function: 	ui_SearchByID
    Args: 		None
    Returns: 	Nothing
    Purpose:	Gets an ID from the user, then display that Record in detail
 ===============================================================================
*/
void ui_SearchByID(void)
{
	struct Record *rec;
	int choice;
	
	if (gl_HighestID == 0)
	{
		printf ("No entries in the database!\n");
		io_EnterToContinue();
		return;
	}
	printf("Enter Record ID Number (1-%d, x to exit) >", gl_HighestID);
	if ((choice = io_GetInt(gl_HighestID)) == RC_BAD)
		return;
	if ((rec = li_GetDataPtrByID(gl_AppData.MainList, choice)) == NULL)
		printf("No such entry: %d\n", choice);
	else rec_PrintLong(rec);
	io_EnterToContinue();
}

/*
 ===============================================================================
 	Function: 	ui_SearchAll
    Args: 		Field type
    Returns: 	Nothing
    Purpose:	Gets search criteria from user, the traverses the list, short
    			printing each record that matches the criteria.
 ===============================================================================
*/
void ui_SearchAll(void *WhichField)
{
	char	buffer[MAXL_FIELD + 1];
	int		i, len;

	if (gl_HighestID == 0)
	{
		printf ("No entries in the database!\n");
		io_EnterToContinue();
		return;
	}
	
	if ((enum RECORD_FIELDS)WhichField == RECF_ID) 
	{
		ui_SearchByID();
		return;
	}
	
	printf("Enter Criteria >");
	
	if ((len = io_GetLine(buffer, MAXL_FIELD + 1, stdin)) != 0)
	{
		gl_AppData.SearchField = (enum RECORD_FIELDS)WhichField;
		for (i = 0; i < len; i++)	/* convert to lower case for search */
			if (isupper(buffer[i])) buffer[i] = tolower(buffer[i]);
		strncpy(gl_AppData.SearchText, buffer, MAXL_FIELD + 1);
		gl_AppData.LinesDisplayedSoFar = 0;
		(void)li_Traverse(gl_AppData.MainList, rec_SearchAndPrint);
	}

	io_EnterToContinue();
}

/*
 ===============================================================================
 	Function: 	ui_DisplayAll
    Args: 		None
    Returns: 	Nothing
    Purpose:	Traverses the list and does a short print of each record
 ===============================================================================
*/
void ui_DisplayAll(void *Dummy)
{
	if (gl_HighestID == 0)
	{
		printf ("No entries in the database!\n");
		io_EnterToContinue();
		return;
	}
	
	gl_AppData.LinesDisplayedSoFar = 0;
	gl_AppData.SearchField = RECF_DUMMY;
	(void)li_Traverse(gl_AppData.MainList, rec_SearchAndPrint);
	io_EnterToContinue();
}

/*
 ===============================================================================
 	Function: 	ui_Save
    Args: 		None (NULL Pointer)
    Returns: 	Nothing
    Purpose:	Gets confirmation from the user, then saves the current list to disk
 ===============================================================================
*/
void ui_Save(void *Dummy)
{
	if (io_GetYesNo("Confirm Save (including purge of deleted records) (y/n) >") == TRUE)
	{
		(void)io_WriteAllRecordsToFile(gl_AppData.MainList);
	}
}

/*
 ===============================================================================
 	Function: 	ui_PurgeDeleted
    Args: 		None (NULL Pointer)
    Returns: 	Nothing
    Purpose:	Gets confirmation from the user, then traverses the list,
    			removing (free'ing) any records that are marked as deleted
 ===============================================================================
*/
void ui_PurgeDeleted(void *Dummy)
{
	unsigned char status = ST_DELETED;
	
	if (gl_HighestID == 0)
	{
		printf ("No entries in the database!\n");
		io_EnterToContinue();
		return;
	}
	
	if (io_GetYesNo("Confirm Purge (y/n) >") == TRUE)
		gl_AppData.MainList = li_DeleteNodeAndData (gl_AppData.MainList, &status, rec_CompareStatus);
}

/*
 ===============================================================================
    Function: 	ui_ShowInfo
    Args: 		None (NULL Pointer)
    Returns: 	Nothing
    Purpose:	Displays stats about the program and the PhoneBook
 ===============================================================================
 */
void ui_ShowInfo(void *Dummy)
{
	printf ("--->\n---> **PhoneBook** - Version: %s\n", PRG_VERSION);
	printf ("---> Written By *Hammer* for the C Programming Contest\n");
	printf ("---> held by http://www.cprogramming.com/cboard/ in May 2002\n");
	printf ("---> You can contact the author via email: claw_hammer@hotmail.com\n");
	printf ("--->\n---> The PhoneBook datafile is %s\n", STR_FILENAME);
	printf ("---> The PhoneBook configuration file is %s\n", STR_CFG_FILENAME);
	printf ("--->\n---> There are currently %d entries loaded.\n", li_Count(gl_AppData.MainList));
	printf ("--->\n---> This program accepts command line information\n");
	printf ("---> For example (assuming the program name is phone.exe):\n");
	printf ("--->   >phone.exe hammer tommy\n");
	printf ("---> will result in the database being searched for the\n");
	printf ("---> names hammer and tommy and the results being displayed.\n");
	printf ("---> In this case, the menu is not loaded, and the\n");
	printf ("---> program terminates.  This is useful for quick lookups.\n");
	printf ("--->\n");
	io_EnterToContinue();
}

/*
 ===============================================================================
 	Function: 	ui_ToggleAutoSave
    Args: 		None (NULL Pointer)
    Returns: 	Nothing
    Purpose:	Allows the user to turn auto-save off and on.
 ===============================================================================
*/
void ui_ToggleAutoSave(void *Dummy)
{
	printf ("Auto-Save is currently %s\n", (gl_AppCfg.AutoSave)?"On":"Off");
	if (io_GetYesNo("Toggle auto-save (y/n) >") == TRUE)
	{
		gl_AppCfg.AutoSave = ~gl_AppCfg.AutoSave;
		printf ("Auto-Save is now %s\n", (gl_AppCfg.AutoSave)?"On":"Off");
		io_EnterToContinue();
	}
}

/*
 ===============================================================================
 	Function: 	ui_SetLinesPerDisplay
    Args: 		None (NULL Pointer)
    Returns: 	Nothing
    Purpose:	Allows the user to set the number of lines displayed in lists
    			before a io_EnterToContinue is called.
 ===============================================================================
*/
void ui_SetLinesPerDisplay(void *Dummy)
{
	int Lines;
	
	printf ("This option determines how many lines are output to the screen\nbefore a \"hit enter to continue\" message is displayed\n");
	printf ("The current setting is %d\n", gl_AppCfg.LinesPerDisplay);
	printf ("Enter a new number or x to exit >");
	
	if ((Lines = io_GetInt(MAX_LINES_PER_PAGE)) == RC_BAD)
		return;
		
	gl_AppCfg.LinesPerDisplay = Lines;
	printf ("The new setting is %d\n", gl_AppCfg.LinesPerDisplay);
	io_EnterToContinue();
}

/*
 ===============================================================================
 	Function: 	ui_SetSortBy
    Args: 		Field type
    Returns: 	Nothing
    Purpose:	Allows the user to set the sort order
 ===============================================================================
*/
void ui_SetSortBy(void *WhichField)
{
	gl_AppCfg.SortField = (enum RECORD_FIELDS)WhichField;
	
	printf ("Sorting the data...");
	li_Sort (gl_AppData.MainList, rec_Compare); 
	printf ("Done!\n");
	io_EnterToContinue();
}
main.h
Code:
/*
 ===============================================================================
 	Author:		Hammer, May 2002, For www.cprogramming.com/cboard/
 	File:		main.h
 	Contents:	All the definitions needed for the app, along with the
 				function prototypes.  This is the only header file used
 				by this program.
 ===============================================================================
*/
#ifndef __PHONE__
#define __PHONE__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/*
 * #defines
 * Field lengths - These do NOT include the 1 byte for the NULL terminator
 */
#define MAXL_NAME		100
#define MAXL_PHONENUM	20
#define MAXL_MISC		256
#define MAXL_FIELD		256
#define MAXL_ADDR		100
#define MAXL_FIELD_NAME 50


#define DEF_LINES_PER_PAGE 10
#define MAX_LINES_PER_PAGE 999

/* 
 * String variables 
 */
#define STR_FILENAME	"phonebk.dat"
#define STR_CFG_FILENAME	"phonebk.cfg"
#define PRG_VERSION 	"1.1"
#define RC_BAD -1

/*
 * boolean style vars 
 */
#define TRUE	1
#define FALSE	0

/*
 * Record status flags 
 */
#define ST_DELETED	0x1

/*
 * Macros 
 */
#define FLUSH_INPUT while(getchar() != '\n')

/*
 * enums
 */
enum RECORD_FIELDS {RECF_DUMMY, RECF_ID, RECF_NAME, 
					RECF_PHONENUM1, RECF_PHONENUM2, RECF_PHONENUM3, 
					RECF_ADDRLINE1, RECF_ADDRLINE2, RECF_ADDRLINE3,
					RECF_ADDRLINE4, RECF_ADDRLINE5, RECF_EMAILADDR,
					RECF_MISC, RECF_END_MARKER};

/*
 * typedefs
 */
typedef int	bool_t;

/*
 * Structures
 */
struct Record
{
	int				ID;						/* Assigned at load time, sequential based on input, not related to tree */
	char			Name[MAXL_NAME + 1];	/* All in name field, holds any type, so user can decide own format */
	char			PhoneNum1[MAXL_PHONENUM + 1];	/* 3 phone num fields, user decides what to put where */
	char			PhoneNum2[MAXL_PHONENUM + 1];
	char			PhoneNum3[MAXL_PHONENUM + 1];
	char			AddrLine1[MAXL_ADDR+1];
	char			AddrLine2[MAXL_ADDR+1];
	char			AddrLine3[MAXL_ADDR+1];
	char			AddrLine4[MAXL_ADDR+1];
	char			AddrLine5[MAXL_ADDR+1];
	char			EmailAddr[MAXL_ADDR+1];
	char			Misc[MAXL_MISC + 1];			/* Freeform text field, for user comments. */
	unsigned char	Status; /* See the ST_... variables */
};

struct Node
{
	void		*DataPtr;
	struct Node *Next;
};

struct appdata
{
	struct Node 		*MainList;
	enum RECORD_FIELDS	SearchField;
	char				SearchText[MAXL_FIELD + 1];
	bool_t				DataChanged;
	int 				LinesDisplayedSoFar;
};

struct appconfig
{
	bool_t AutoSave;
	int LinesPerDisplay;
	enum RECORD_FIELDS	SortField;	
};

struct menu_item
{
	char	*Text;
	void 	(*fptr) (void *);
	void 	*args;
};

/*
 * more typedefs
 */
typedef void (*FPTR_Action) (void *);
typedef int (*FPTR_Compare) (const struct Record *, const struct Record *);
typedef int (*FPTR_CompareDelete) (const struct Record *, void *);

/*
 * Global vars / externs
 */
extern int	gl_HighestID;
extern struct appdata gl_AppData;
extern struct appconfig gl_AppCfg;
extern struct menu_item MainMenu[];

/*
 * Prototypes for ListUtilities
 */
struct Node *li_Create(void);
struct Node *nd_Create(void *, struct Node *);
void		nd_Destroy(struct Node *);
int			li_Traverse(struct Node *, FPTR_Action);
struct Node *li_Insert(struct Node *, void *, FPTR_Compare);
struct Node *li_Destroy(struct Node *);
struct Record *li_GetDataPtrByID(struct Node *, int );
struct Node *li_DeleteNodeAndData(struct Node *, void *, FPTR_CompareDelete);
int 		li_Count(struct Node *);
void li_Sort(struct Node *, FPTR_Compare);

/*
 * Prototypes for RecordUtilities
 */
int			rec_Compare(const struct Record *, const struct Record *);
int			rec_CompareID(const struct Record *, void *);
int 		rec_CompareStatus(const struct Record *rec, void *);
void		rec_PrintShort(void *);
void 		rec_PrintLong(void *);
void		rec_SearchAndPrint(void *);
char 		*rec_GetFieldPointer(const struct Record *, enum RECORD_FIELDS);

/*
 * Prototypes for ioUtilities
 */
void		io_WriteRecordToFile(void *);
struct Node *io_LoadPhoneBookFromFile(struct Node *);
int			io_WriteAllRecordsToFile(struct Node *);
int 		io_GetInt(int);
void 		io_EnterToContinue(void);
int 		io_GetLine(char *, int , FILE *);
bool_t		io_GetYesNo(char *);
void 		io_LoadAppConfig(void);
void 		io_SaveAppConfig(void);

/*
 * Prototypes for UserInterfaceUtilities
 */
void 		ui_AddNewEntry(void *);
void 		ui_DeleteEntry(void *);
void 		ui_UndeleteEntry(void *);
void 		ui_UpdateEntry(void *);
void 		ui_AddOrUpdateEntry(struct Record *);
void 		ui_DisplayAll(void *);
void 		ui_Save(void *);
void 		ui_PurgeDeleted(void *);
void 		ui_SearchAll(void *);
void 		ui_SearchByID(void);
void 		ui_DisplayMenu(void *);
void 		ui_ShowInfo(void *);
void 		ui_ToggleAutoSave(void *);
void 		ui_SetLinesPerDisplay(void *);
void 		ui_SetSortBy(void *);
#endif
ygfperson is offline  
Old 05-27-2002, 06:44 PM   #7
Just because
 
ygfperson's Avatar
 
Join Date: Jan 2002
Posts: 2,502
and... Hammer wins! Hail to the Hammer!
Cicero... there's always the next one.
good job to the contestants, and thanks to the judges and their hard work.
ygfperson is offline  
Old 05-28-2002, 02:31 AM   #8
Guest
 
Sebastiani's Avatar
 
Join Date: Aug 2001
Posts: 4,923
Congratulations to Hammer, and Cicero, too...they both deserve credit for their effort. Hammer's entry was very complex, and a little hard to follow sometimes. But all in all he deserves the most credit for his hard work. Cicero, on the other hand was better at stubbing out the implementation first, a wise move. But his implementation seemed a little weak. So I would have chosen Hammer for that reason. Cicero, Hammer, what have you two got to say about the results?
Sebastiani is offline  
Old 05-28-2002, 03:56 AM   #9
Nerd
 
Xmevs's Avatar
 
Join Date: Apr 2002
Posts: 146
Congrats to Hammer.
Xmevs is offline  
Old 05-28-2002, 04:12 AM   #10
End Of Line
 
Hammer's Avatar
 
Join Date: Apr 2002
Posts: 6,240
Hey, what d'ya know... I won!!

Naturally I'm pleased, and would like to pass my thanks to all those involved, and my condolences to Cicero.

I don't have a speech prepared, you'll be glad to hear, nor do I have a mile long list of people to thank!

I have attached a copy of my source, zipped, with a Windows exe. If anyone wants to actually make use of this program, they are more than welcome to (just leave my name in there!), I Also welcome any more comments on it.

As for the comments by the judges, they're all fair and valid, and it's nice to see that all entries were given a fair chance, and were thoroughly examined. I like to know what other people think of my ideas, and this is definately a good way to do it.

The only thing I would like to discuss further is my choice of list. I didn't use a tree because the search facility was designed to check each and every node (it used strstr() to find strings within strings in any given field). If the list was a tree, it would have to traverse all the way to the left doing nothing, then all the way back to the far right, doing compares on each node as it went. Using the link list method, you just start at the beginning and traverse to the end, therefore isn't this faster? I admit that adding a new node would be slower, but as most of the time the user will be searching, rather than adding, I thought link list was the way to go. What do you think?

One bad point about the contest: only 2 entries. I'd have liked to seen more versions of the code to see how else it could have been done. Maybe next time...... Speaking of which, what is it and when?

Attached Files
File Type: zip hammersphonebookv101.zip (50.5 KB, 58 views)
__________________
When all else fails, read the instructions.
If you're posting code, use code tags: [code] /* insert code here */ [/code]
Hammer is offline  
Old 05-28-2002, 08:30 AM   #11
Code Goddess
 
Prelude's Avatar
 
Join Date: Sep 2001
Posts: 9,661
Quote:
The only thing I would like to discuss further is my choice of list. I didn't use a tree because the search facility was designed to check each and every node (it used strstr() to find strings within strings in any given field). If the list was a tree, it would have to traverse all the way to the left doing nothing, then all the way back to the far right, doing compares on each node as it went. Using the link list method, you just start at the beginning and traverse to the end, therefore isn't this faster? I admit that adding a new node would be slower, but as most of the time the user will be searching, rather than adding, I thought link list was the way to go. What do you think?
A good and valid reason. A binary tree searches no faster in the worst case situation than a linked list but insertion into the tree is quadratic. Though you still sacrifice efficiency since most users will search primarily for the name of the record instead of less memorable fields. Since you perform quite a bit of sorting, reading sorted data into a binary tree is expensive, and balancing routines are such a pain, I'd have to say that a linked list is a good implementation choice but could be better if specialized for searching. You lose the simplicity you have now, but consider how a skip list would improve your searching for a name in a sorted list.

-Prelude
__________________
My best code is written with the delete key.
Prelude is offline  
Old 05-28-2002, 09:30 AM   #12
End Of Line
 
Hammer's Avatar
 
Join Date: Apr 2002
Posts: 6,240
Thanks for your reply Prelude.....

>but consider how a skip list ....
What, there's another type of list?!! Guess I'll have to go read up about that one now.
__________________
When all else fails, read the instructions.
If you're posting code, use code tags: [code] /* insert code here */ [/code]
Hammer is offline  
Old 05-28-2002, 10:27 AM   #13
Code Goddess
 
Prelude's Avatar
 
Join Date: Sep 2001
Posts: 9,661
>What, there's another type of list?!! Guess I'll have to go read up about that one now.
You'll have fun with that one, imagine a linked list with extra links that skip over a certain number of nodes to speed up searching. So you have the lowest level which is a regular list, then another level with a link every 3rd or so node. It's a lot like a binary tree, and is often considered as an alternative.
http://www.google.com/search?hl=en&i...s%3A+Skip+list

-Prelude
__________________
My best code is written with the delete key.
Prelude is offline  
Old 05-29-2002, 09:35 PM   #14
Banned
 
Troll_King's Avatar
 
Join Date: Oct 2001
Posts: 1,784
Good job Son, I like your program.
Troll_King is offline  
Old 05-30-2002, 03:49 AM   #15
End Of Line
 
Hammer's Avatar
 
Join Date: Apr 2002
Posts: 6,240
Quote:
Originally posted by Troll_King
Good job Son, I like your program.
Thanks Dad
__________________
When all else fails, read the instructions.
If you're posting code, use code tags: [code] /* insert code here */ [/code]
Hammer is offline  
 

Thread Tools
Display Modes

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Obfuscated Code Contest: The Results Stack Overflow Contests Board 29 02-18-2005 05:39 PM
Text Compression Contest -- Sept. 28, 2002 ygfperson A Brief History of Cprogramming.com 4 10-03-2002 03:20 PM
Text Compression Contest -- Sept. 28, 2002 ygfperson C++ Programming 5 10-03-2002 03:20 PM
Text Compression Contest -- Sept. 28, 2002 ygfperson C Programming 3 10-01-2002 06:25 PM
Results for the Encryption Contest -- June 23, 2002 ygfperson A Brief History of Cprogramming.com 18 07-07-2002 08:04 AM


All times are GMT -6. The time now is 11:42 PM.


Powered by vBulletin® Version 3.8.1
Copyright ©2000 - 2009, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.3.0 RC2

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22