LiteC and Lists

Part III

 

 

By 3DSki

3/2007

 

 

Introduction

 

In part one of this series of tutorials, I had planned to cover more material, but felt it would become overwhelming.  So, here we are in part two.

 

In part one, we discussed the use of a one-way, or single-link list as an alternative to using an array, in situations where you want a list to change size.  We looked at how a list item is represented, how we create them, how to add them to a list, how to find an item, how to delete an item, and how to free the created items from memory.  I think it best to skip a long summary of that work and direct you to reading part 1, if you haven’t already.  However, I will be repeating some of the basic code here, so if you feel comfortable with linked lists and curious about what follows, please feel free to stick around!

 

At the end of part one, I presented a problem for exploration, concerning the splitting of a list.  I’ve selected this to be the topic of this tutorial installment.  The problem of splitting a list is an interesting one and comes with its own subset of problems.  Also, it is a task that some might want to do in a complex game.  Suppose you have a single player, but then others join this player’s group (forming a list).  Then, members of this group split off, forming a new group, be they friend or foe.  Perhaps this process can occur in a complex game and numerous groups can be formed out of existing ones.  This is only one instance where linked lists, and the splitting of them, might be beneficial.

 

So, with that being said, let’s head on to exploring the splitting a linked list!

 

 

Identity Crisis

 

Programming is an interesting endeavor.  You can start hitting a problem head on, only to find you have some other basic problems that should have been addressed.  Concerning the topic of splitting a linked list, one would be very tempted to start fiddling with items and their links, but you’ll find you will end up with an “identity crisis”.  The identity crisis involves how you identify a list, and when you split them, how you will identify any one of a number of lists.  In short, you have to plan for what global variables you’re going to use, how they are going to be used, and determine if you are going to have problems with them.

 

In the last tutorial, we used some special global variables named “start” and “end” to identify the start and end of our one-and-only linked list.  No sweat… they served their purpose and we accomplished what we wanted.  But, the use of the globals, “start” and “end” goes out the window when looking at goal of creating numerous lists.  All of a sudden all our code that relied on the “start” and “end” globals goes down the tubes, when it comes to working with additional lists being dynamically created. 

 

I say this, because a first solution might be to create another set of global “start” and “end” variables called “start2” and “end2”.   Our previous methods were not written to work with “start2” and “end2”, but only the “start” and “end” variables.  Plus, the solution of adding additional global variables limits the number of lists (and list splits) you can process in your application.  However, if you know you will only want N number of lists, I will agree that creating additional globals may be an option.  However, you would have to add a parameters to your functions named “start” and “end” where they are needed, and pass in “start” or “start2”, etc., as needed.  For example:

 

CHARACTER* start;

CHARACTER* end;

CHARACTER* start2;

CHARACTER* end2;

 

void add( CHARACTER* link, CHARACTER* start, CHARACTER* end)

{

       link.next = NULL;  // "next" is always NULL for a new link

       if ( start == NULL )  // if the list is empty...

       {

              start = link;

       } else {

              end.next = link;

       }

       end = link;

}

 

A = malloc( sizeof( CHARACTER ) );

add( A, start2, end2);

 

B = malloc( sizeof( CHARACTER) );

add( B, start, end);

 

The example code summarizes the coding that would be involves with using a strategy of using multiple start and end global variables to track the start and end of each list created.  All you’d have to change is the number of parameters passed to the existing functions you already have written to handle a single list, taking care to name these parameters “start” and “end”.

 

However, I personally feel that it’s awkward to have to pass these extra data items into all the functions that need them.  I would also prefer not to hard code something that only fits the needs of this program, when I can code it to be flexible enough to fit the needs of most other situations so I’m more likely to be able to reuse the code of my functions.  One problem I have with the above technique is, the “start”s and “end”s are vital pieces of information that are already global and vulnerable, and now they’re passing different ones around into numerous functions.  It was bad enough that we did this with our original code with one list, let alone with multiple ones.  I propose another solution that will hide and protect the all important “start” and “end” of any list, out of a number of any lists that are coexisting at one time.

 

In short, I propose that we bring an new LIST structure into our code, which will contain our “start” and “end”!  This solves a number of potential problems and sets up the ability to dynamically create a list.  First of all, this will allow us to tuck our “start” and “end” away into a container, protecting them from variable name collisions that could occur as your program’s code base grows.  And, just as you can dynamically create a list item, this technique allows you to dynamically create a list:

 

typedef struct list{

       void* start;

       void* end;

       int count;

} LIST;

 

You may notice something odd, if you are coming away from part one of these tutorials.  In that tutorial, we used an item of type, CHARACTER, thus the type of “start” and “end”.   You will have expected CHARACTER, not “void*”, when declaring “start” and “end” in this structure definition.  What the heck is this “void*”?

 

Well, what “void*” means is, “I can store any type of pointer.”!  Now, once again, we gain some real power here, but it comes with some added responsibility.  However, we are on our way to making our code a lot more flexible.  We will still have to change some of our existing functions, but they will be much more reusable once we get done.

 

You may have also noticed that I’ve thrown in a “count” member, as well.  One of the common things you might want to be able to determine in you code is, how many items a list currently holds.  This is an ideal place to add this kind of information for a list.

 

We still have another problem that we can’t get around… sad.  We, will still have to place some kind of limit on the number of lists we create, but I have an ideal for a flexible way for handling this:

 

#define MAX_LISTS   10

 

LIST* gLists[ MAX_LISTS ];

int gListCount;

 

There is no way to get around having to manage our list of lists, but this is a good start.  The “#define” directive let’s you predetermine how many lists you are going to allow, which will then automatically adjust the size of the gCharacterList array of LIST* items.  We must also have a in place a basic means for know how many lists we have and which is the next available slot that can hold a new list, so “gListCount” is used for this purpose.  We must also initialize our LIST* array items to NULL accordingly:

 

void initializeLists( LIST* lists[] )

{

       int i;

       for(i = 0; i < MAX_LISTS;i++)

       {

              lists[i] = NULL;

       }

       gListCount = 0;

}

       …

void main()

{

       initializeLists( gLists );

       …

}

 

The above code is way simple.  It uses our MAX_LISTS defined valued to loop through all LIST* items in the array and sets them to NULL.   And, since we’re initializing things for our LIST* array here, we set our list count variable to 0.  Notice that even though gLists[] is global, I decided to pass it into the function.  This helps to remind us that it is going to do something with that array.  Another important fact to point out concerning the passing of this array to the function is, the whole array is not actually being passed into the function, but only the address of the array.

 

Now, we’re just about ready to move on in our discussion.  As you can see, we had to make some important decisions to set a good foundation for the rest of our work.  We now have a structure to contain a list, as well as a container to contain a variable number of lists.  We just couldn’t start splitting lists until we came up with a solid means for creating and keeping track of these new lists.  We have one more detail to take care of.  We need to look ahead and determine how we are going to handle the deletion of a list, from of lists of lists.

 

Adding is simple.  We use gListCount as the index into the gCharacterLists array for the item we want to add.  It starts out a 0, where our first item will be stored.  After we create and add a new list, we bump it up by 1; ++gListCount.  However, if we’re not careful, our indexing will get all messed up, because we don’t know which position a list has been deleted from… at least not yet.  We may have to go back and make some adjustments to our existing code… but better now than later!

 

Let’s store the index where a list is saved in the list itself.  This will allow us to visit our main list container and compact it, starting at the location where the list is removed:

 

typedef struct list{

       int type;

       int position;

       void* start;

       void* end;

       int count;

} LIST;

 

I’ve added a member called “position” to our LIST definition.  When we create a list, we will set this member equal to the current value of gListCount, prior to bumping its value up in preparation for the next list to be added.

 

I’ve also added a member called, “type”.  The “type” member will be used to help our process determine what type of items are in a particular list.  It is important to note that we can store different item types in a list, but we creating setting up a situation where different lists hold their own items of a single type.

 

Let’s go ahead and create our list compacting utility.  To simplify this utility, we will assume that another function has taken care of the memory management of the list items themselves.  After all, we already have a function that does the clean-up of the memory used by the list items that we want to delete.  Here, we just want to simply remove the LIST from memory and compact gCharacterLists:

 

void deleteList( LIST* list )

{

       int startIdx;

       int i;

       if ( list != NULL )

       {

              startIdx = list.position;

              freeList( list );          // …free the list’s data items

              free( list );        // …free the list

              gLists[startIdx] = NULL;

              for( i = startIdx; i < gListCount;i++ )

              {

                     if ( gLists[i+1] == NULL) break;

                     gLists[i] = gLists[i+1];

                     gLists[i].position = i;           //…reset to new position

                     gLists[i+1] = NULL;        //…empty space of moved item

              }

              --gListCount;

              if ( gListCount < 0)

                     gListCount = 0;

              return 1;

       }

       return 0;

}

 

In the above code, startIdx is used to hold the gLists array index where the list is being deleted from, which we get from the list’s “position” member.  The i variable is used for our “for” loop that will do the compacting to fill the empty space created by the removal of the list.  First we check to make sure the list is valid.  If it is, we get its position and store it in startIdx and then free the actual LIST structure from memory.  Once this is done, we start moving all the remaining lists that are higher in the array down to the empty space that precedes it.  Finally, we simply adjust gListCount by subtracting 1 from it, making it ready to add any new lists to the end of our compacted lists array.

 

 

Adjusting the Item Definitions

 

Now, we still have a bit of a problem with our items if we use the definition we used in part one of the tutorials.  We are attempting to make our list processing as flexible as possible to minimize recoding in the future.  The problem we still have relates back to the identity crisis we spoke of earlier. 

 

LiteC, and C itself, for that matter, are very concerned with each type of data were working with.  You can’t be working with an CHARACTER* item, then all of a sudden magically process an ANIMAL* item.  Earlier, we made our “start” and “end” LIST* members “void*” to help make them generic… hold any type of pointer.  However, when we want to do some processing that requires either of these items, we need to cast them to their proper data type:

 

ANIMAL* a = (ANIMAL*) start;

CHARACTER* c = (CHARACTER*) start;

 

Because of this, we are going to revisit the issue of the “start” and “end” members of our LIST structure, as well as the definition of the CHARACTER item we want to carry over from the first tutorial.  What we want to move toward is a more generic item type, since this is what is used most in the common processes, like “find”, “add” and “delete”.  I propose we redefine our CHARACTER structure, and create an additional ITEM structure.  This means we’ll change our LIST definition as well, to match type of our  “start” and “end”.  We also add a “type” member to ITEM, so we can match the LIST and ITEM types:

 

typedef struct data

{

   char letter[2];

   struct character* next;

} ITEM_DATA;

 

typedef struct item

{     

       int type;

       void *data;

       ITEM* next;

} ITEM;

 

typedef struct list{

       int type;

       int position;

       ITEM* start;

       ITEM* end;

       int count;

} LIST;

 

Why are we doing this?  In our common processing, such as  “add”, “find” and “delete” we are most concerned with processing an item in general and its “next” pointer.  By creating a commonly used ITEM, which contains a commonly used “next”, we come that much closure to reusing the “add”, “find” and “delete” processing.  The new “void* data” item in our ITEM structure allows us to store any type of data into it, separating out the data that’s specific to our application.  Now, the “add”, “find” and “delete” processing can always work with the type, ITEM.  If we don’t use this type of strategy, we would have to rewrite “add”, “find” and “delete” for each new application that requires a different type of data; CHARACTER, ANIMAL, etc.

 

 

More About “Type”

 

Once again LiteC, and C, are very picky about data types.  You always eventually have to specify the type of data you are working with.  This is the reason we added the “int type” member into our definition of the LIST, to specify the type of items being store in it.  We need to come up with the scheme for identifying the data being store in each of our lists.

 

#define CHARACTER_TYPE     0

#define ANIMAL_TYPE 1

 

The above scheme is simple and provides a “code” for each of the data types that our lists will hold.

 

I know this all seems quite complex, but in reality, none of the individual things I’ve introduced here is complex.  It’s just that the number of simple changes have increased.  I hope it will all come together for you when we actually start to use this alternative strategy for defining our lists and the items they hold.  We are finally prepared to move on to our core processing. 

 

 

Creating A List

 

#define CHARACTER_TYPE     0

#define ANIMAL_TYPE 1

#define MAX_LISTS   10

 

typedef struct character

{

   char letter[2];

} CHARACTER;

 

typedef struct animal

{

   char name[30];

} ANIMAL;

 

 

typedef struct item

{     

       int type;

       void *data;

       ITEM* next;

} ITEM;

 

typedef struct list{

       int type;

       int position;

       ITEM* start;

       ITEM* end;

       int count;

} LIST;

 

LIST* temp;

LIST* gLists[ MAX_LISTS ];

int gListCount;

 

void initializeLists( )

{

       int i;

       LIST* current;

       for(i = 0; i < MAX_LISTS;i++)

       {

              gLists[i] = NULL;

       }

       gListCount = 0;

}

 

LIST* createList(int listType)

{

       if ( gListCount < MAX_LISTS )

       {

              if ( listType ==  CHARACTER_TYPE  || listType == ANIMAL_TYPE )

              {

                     gLists[gListCount] = malloc( sizeof(LIST));

                     if ( gLists[gListCount] != NULL)

                     {

                           gLists[gListCount].start = NULL;

                           gLists[gListCount].end = NULL;

                           gLists[gListCount].position = gListCount;

                           gLists[gListCount].listType = listType;

                           gListCount++;

                           return gLists[gListCount-1];

                     }

              }

       }

       return NULL;

}

 

int addItem( LIST* list, ITEM* item)

{

       ITEM* end;

      

       if ( list != NULL && item != NULL && item.listType == list.listType )

       {

              if ( list.start == NULL )  // if the list is empty...

              {

                     list.start = item;

              } else {

                     end = list.end;

                     end.next = item;

              }

              list.end = item;

              return 1;

       }

       return 0;

}

 

void freeList(LIST* list)

{

       ITEM* current;

       void* data;

       current = list.start;

 

       while( current != NULL)

       {

              list.start = current.next;

              data = current.data;

              free(data);

              free(current);

              current = list.start;

       }

}

 

void deleteList( LIST* list )

{

       int startIdx;

       int i;

       if ( list != NULL )

       {

              startIdx = list.position;

              freeList( list );    // …free the list’s data items

              free( list );        // …free the list

              gLists[startIdx] = NULL;

              for( i = startIdx; i < gListCount;i++ )

              {

                     if ( gLists[i+1] == NULL) break;

                     gLists[i] = gLists[i+1];

                     gLists[i].position = i;    //…reset to new position

                     gLists[i+1] = NULL;        //…empty space of moved item

              }

              --gListCount;

              if ( gListCount < 0)

                     gListCount = 0;

              return 1;

       }

       return 0;

}

 

void main()

{

       // For CHARACTER work...

       LIST* characterList;

 

       initializeLists( );  // set all lists to NULL (no lists)     

       …

 

       characterList = createList( CHARACTER_TYPE );

       …

 

       deleteList(temp);

}

 

 

The code above pulls together everything we’ve discussed so far. Hopefully it makes more sense seeing the actual code in use.  But, so far, we’ve only created a LIST.  We now have to create an item to place in the list.

 

Previously, in the earlier tutorial, we simply created our item in a straightforward fashion.  Now, creating an item needs to be done in three phases.  First, we need to allocate memory for the ITEM*.  Next, we need to allocate memory for the data our item will hold.  Finally, we need to set the item’s “data” and “type” members.  The “data” element is set to our own custom data structure (ANIMAL or CHARACTER in this exercise).  I propose to write a function to do this to make later use less of a hastle:

 

ITEM* createItem( int listType )

{

       ITEM* tempItem;

       CHARACTER* tempCharacter;

       ANIMAL* tempAnimal;

      

       tempItem = malloc( sizeof(ITEM) );

       tempItem.next = NULL;

 

       if (listType == CHARACTER_TYPE )

       {

              tempItem.listType = CHARACTER_TYPE;

              tempCharacter = malloc( sizeof (CHARACTER) );

              tempItem.data = (void*)tempCharacter;

       }      else {

              if (listType == ANIMAL_TYPE )

              {

                     tempItem.listType = ANIMAL_TYPE;

                     tempAnimal = malloc( sizeof (ANIMAL) );

                     tempItem.data = (void*)tempAnimal;

              } else {

                     free(tempItem);

                     return NULL;

              }

       }

       return tempItem;

}

 

In the code above, we simply create an ITEM of the desired type, as well as an empty ANIMAL or CHARACTER structure to hold our custom data.  We still need to set the values within a new ANIMAL or CHARACTER structure.  We can do this with a couple of custom functions, one for each item data type:

 

char* strcpy(char* a, char* b);

strcpy = DefineApi("mscvrt!strcpy");

ITEM* populateAnimal( ITEM* item, char* name )

{

       ANIMAL* tempAnimal;

       if( item != NULL && name != NULL )

       {

              if ( item.listType == ANIMAL_TYPE )

              {

                     tempAnimal = (ANIMAL*) item.data;

                     strcpy( tempAnimal.name, name );

                     return item;

              }

              // else, ERROR: item is not of the type we are asking to populate.

       }

       // else, ERROR: item and/or passed in data are NULL

       return NULL;

}

 

ITEM* populateCharacter( ITEM* item, char* letter )

{

       CHARACTER* tempCharacter;

      

       if( item != NULL && letter != NULL )

       {

              if ( item.listType == CHARACTER_TYPE )

              {

                     tempCharacter = (CHARACTER*) item.data;

                     strcpy( tempCharacter.letter, letter );

                     return item;

              }

       }

       // else, ERROR: item and/or passed in data are NULL

       return NULL;

}

 

Both of the functions presented above follow the same basic logic.  First, we do some checking to make sure our request is valid; our LIST* (item) should not be NULL and the character string we passed in should be set to a literal string; “A”, “elephant”, etc.  More checks could be added.  For instance, you might want to make sure the string passed in isn’t to large for the ANIMAL or CHARACTER data item(s); CHARACTER’s letter only holds 1 char, plus C’s end-of-string NULL character, and ANIMAL’s “name” only holds 29 characters, plus C’s end-of-string NULL character.  However, we’ve got the basic logic we need so far to get the job done.

 

Each of the functions, populateAnimal() and populateCharacter() demonstrate why we need to identify and store the “type” of item and how we use this information to properly cast the data to that type:

 

ANIMAL* Cast:

 

tempAnimal = (ANIMAL*) item.data;

 

CHARACTER* Cast:

 

tempCharacter = (CHARCTER*) item.data;

 

If you fail to cast item’s “data” to the appropriate type, your code will either not compile, or it will crash with an error when you try to access the ANIMAL* or CHARACTER* members (“name” and “letter”).  Why?  Simply put, “void*” does not know anything about “name” or “letter”, which are specific to your own ANIMAL* and CHARACTER* instances.

 

So, now let’s add the use of this additional code to our main():

 

void main()

{

       // First time initialization of our gLists array…

       initializeLists();

 

       // Creation of a list that holds CHARACTER* items

       characterList = createList( CHARACTER_TYPE );

 

       // Create a CHARACTER* item to add to our list.

       tempCharacter = createItem(CHARACTER_TYPE);

       populateCharacter( tempCharacter, “A” )

       …

       deleteList(temp);

}

 

Now, we’ve created a list and we’ve created an item for the list, but we’ve not yet added it to the list.  We have to adjust the code for the add() function we used in the previous tutorial:

 

Original Add Item:

 

void add( CHARACTER* link)

{

       link.next = NULL;  // "next" is always NULL for a new link

       if ( start == NULL )  // if the list is empty...

       {

              start = link;

       } else {

              end.next = link;

       }

       end = link;

}

 

New Add Item:

 

int addItem( LIST* list, ITEM* item)

{

       ITEM* end;

      

       if ( list != NULL && item != NULL && item.listType == list.listType )

       {

              if ( list.start == NULL )  // if the list is empty...

              {

                     list.start = item;

              } else {

                     end = list.end;

                     end.next = item;

              }

              list.end = item;

              return 1;

       }

       return 0;

}

 

The differences between the original and new “add item” processing are minor.  First, to control what list we are adding the item to, we must now pass in the LIST*.  We are now working with our generic ITEM*, rather than a specific CHARACTER*, so this gets passed in.  The key difference is, our “start” and “end” for the specific list is stored in the LIST*, so we are setting these.  If the list is empty, then we must set list.start to this new initial item, else we add the new item to the end of the list and adjust list.end to be set to the new item.  Notice that we also added a check to make sure the “type” of the item matches the list’s “type”.  We return 1 (true) if the types are the same and the item is added to the list, or 0 (false) if the add is unsuccessful.

 

It is important to note that item.next was already set to NULL when we created the ITEM* to hold our custom data.  In this “add item” processing, we are always adding new ITEM*’s to the end of the list.  You would have to add your own additional functions if you wanted to insert items at the beginning or center of the list.  For this example we can now add the ITEM* to the list:

 

       …

       // First time initialization of our gLists array…

       LIST* characterList;

       ITEM* tempCharacter;

 

       initializeLists();

 

       // Creation of a list that holds CHARACTER* items

       characterList = createList( CHARACTER_TYPE );

 

       // Create a CHARACTER* item to add to our list.

       tempCharacter = createItem(CHARACTER_TYPE);

       populateCharacter( tempCharacter, “A” )

 

       // Add the populated item to our list

       addItem(characterList, tempCharacter);

       …

 

Note that the above code does not include the logic that should be used to check the return value of the functions we’re using to create a list, create an item, populate the item and add the item to the list.  However, in practice I highly encourage you to do so!  Adding these checks will greatly help you find bugs during development.

 

 

Refactoring

 

I’d like to step of the topic here a bit and talk about refactoring.  “Refactoring” is the process of pulling out common logic and separating this logic into a function.  There are some pieces of code above that you could choose to refactor.

 

The code for our item creation is a little messy as well.  I would opt to create an item creation function for each item type:

 

ITEM* createCharacterItem( char* letter)

{

       CHARACTER* tempCharacter;

      

       tempCharacter = createItem( CHARACTER_TYPE );

       if ( tempCharacter != NULL )

       {

              return populateCharacter( tempCharacter, letter );

       }

       return NULL;

}

 

ITEM* createAnimalItem( char* name)

{

       ANIMAL* tempAnimal;

       tempAnimal = createItem(ANIMAL_TYPE);

       if ( tempAnimal != NULL )

       {

              return populateAnimal( tempAnimal, name );

       }

       return NULL;

}

 

In the above item creation methods, we simple place our original lines of code into the function and added some of the error checking that we should have added to begin with.  Now our top level code in main() will look something like this:

 

temp = createList( CHARACTER_TYPE );

tempCharacter = createCharacterItem(“A”);

addItem(temp, tempCharacter );

tempCharacter = createCharacterItem(“B”);

addItem(temp, tempCharacter );

tempCharacter = createCharacterItem(“C”);

addItem(temp, tempCharacter );

 

Now, let’s look at a strategy for finding an item in our list.  Our perspective will be as in the previous tutorial, where we are finding an item based on data in contains; for instance, the CHARACTER* item whose “letter” equals “B”.  We will want to add an advanced technique to our “find” function, but first we have to adjust the one we created in the first tutorial, just like we did with our list and item creation functions:

 

Original Find:

 

int strcmp(char* a, char* b);           

strcmp = DefineApi("mscvrt!strcmp");

CHARACTER* find( char* value)

{

       current = start;

 

       while( current != NULL )

       {

              if ( strcmp( current.letter,value) == 0)

                     return current;

              current = current.next;

       }

       return NULL;

}

 

New Find:

 

int strcmp(char* a, char* b);           

strcmp = DefineApi("mscvrt!strcmp");

int compareString( ITEM* item, char* value );   // …prototype/function pointer

 

int characterCompare(ITEM* item, char* value)

{

       CHARACTER* charItem;

       charItem = (CHARACTER*) item.data;

       return ( strcmp(charItem.letter, value) == 0 );

}

 

int animalCompare(ITEM* item, char* value)

{

       ANIMAL* charItem;

       charItem = (ANIMAL*) item.data;

       return ( strcmp(charItem.name, value) == 0 );

}

 

ITEM* findItem( LIST* list, char* value, void* functPtr)

{

       ITEM *current;

       current = list.start;

 

       compareString = functPtr;

       while( current != NULL )

       {

              if ( compareString(current, value) )

                     return current;

              current = current.next;

       }

       return NULL;

}

ITEM* item;

item =  findItem(characterList, “B”,  characterCompare );

item =  findItem(animalList, “elephant”,  animalCompare );

 

Very little has actually changed with the function we use to find an item, but the small changes are significant!  First, our new findItem() function must return an ITEM* instead of a CHARACTER*.  The other difference is, we pass in the LIST* for the list we want to search.  Remember, it holds the “start” property we need to use to begin to loop through each item of the list.  Obviously, we still have to pass in the string (char*) literal related to the item we want to find.  All this is pretty straightforward.  But, now we’re ready for the advanced feature we’ve added that requires some explanation!

 

In short, we are going to work with a function pointer that will allowing us to pass in custom routines to the findItem() function to help us find either an ANIMAL or a CHARACTER item.  First we define a global generic function pointer by creating a function prototype:

 

int compareString( ITEM* item, char* value ); // …prototype/function pointer

 

Next, we current are planning to look for CHARACTER* data items and ANIMAL* data items.  Therefore we need two separate functions, one to work for each:

 

int characterCompare(ITEM* item, char* value)

{

       CHARACTER* charItem;

       charItem = (CHARACTER*) item.data;

       return ( strcmp(charItem.letter, value) == 0 );

}

 

int animalCompare(ITEM* item, char* value)

{

       ANIMAL* charItem;

       animalItem = (ANIMAL*) item.data;

       return ( strcmp(animalItem.name, value) == 0 );

}

 

What’s special about these functions is, we plan to have findItem()  pass in the current ITEM* its at in the list.  We need to do this, because our custom methods need to cast the data contents of the ITEM* to the ANIMAL* or CHARACTER* type, then compare the value we are looking for, to either the animal’s “name” or the character’s “letter’ property.  Notice in that when we call findItem our last parameter is a “void*”, which is the address, or pointer, to one of our two custom functions (characterCompare() or animalCompare()).  We pass this to the function by simply providing the name of the function, without the parens and function arguments:

 

item =  findItem(characterList, “B”,  characterCompare );

 

Now, finally, inside of findItem(), we must use the global function prototype’s name as the caller, by setting it to the value of the parameter:

 

compareString = functPtr;

 

Inside the “while” loop, we can use the global function pointer, compareString() to call the custom function we passed in:

 

       if ( compareString(item, value) ) { … }

           

 

Freeing a List

 

Let’s return to our deleteList() function.  Previously, we only freed the LIST*, removed it from the gLists[], then compacted gLists[] so it wouldn’t have any empty slots.  However, it would help our coding if we went ahead and freed the items it contains as well. 

 

void freeList(LIST* list)

{

       ITEM* current;

       void* data;

       current = list.start;

 

       while( current != NULL)

       {

              list.start = current.next;

              data = current.data;

              free(data);

              free(current);

              current = list.start;

       }

}

 

void deleteList( LIST* list )

{

       int startIdx;

       int i;

       if ( list != NULL )

       {

              startIdx = list.position;

              freeList( list );

              free( list );

              gLists[startIdx] = NULL;

              for( i = startIdx; i < gListCount;i++ )

              {

                     if ( gLists[i+1] == NULL) break;

                     gLists[i] = gLists[i+1];

                     gLists[i].position = i;

                     gLists[i+1] = NULL; 

              }

              --gListCount;

              if ( gListCount < 0)

                     gListCount = 0;

              return 1;

       }

       return 0;

}

 

To summarize, we adjusted freeList() to take and use our LIST*, just like we did with the other functions we adjusted from first tutorial.  Then, just before we call free( list ) we added the call to freeList( list ).  Now, when we delete a list, we know that all the data in the list will be freed.  Remember, if you “malloc” data, you have to free it… if you “malloc” 10 separate data items, then you have to free all 10 data items; your custom data, and the ITEM.

 

 

Finishing Up

 

We just about finished up our own linked list management API that can be applied to multiple lists.   However, we haven’t covered several items that we covered in the first tutorial, such as findPrevious() and deleteItem().  Since I’ve walked you through the adjustments made to the other list item utility functions, I’ll direct you to make the same type of adjustments to these two methods for our new list management API.  This will give you an activity to play around with to help practice the principles used here and develop a better understanding of how the code works.

 

 

Recap

 

We still haven’t split a list, and we’re almost 20 pages into the tutorial!  However, I felt it important to work through our multiple list management problem and make adjustments that would make the code more useful for future use.  Before we actually split a list, I thought we should recap the techniques used, especially since we’ve covered so much material to create this list management API.

 

We learned how to create an array of a new LIST* data type and place a size limit on the size of this array using the #define directive.  This #define directive allows us to quickly change the limit of the number of lists allowed in an application.

 

This new LIST* data type allows ups to store the position of the list in the lists array, the type of list it is, the number of items in the list, as well as the all important “start” and “end” properties of the list.

 

We created a generic ITEM* data type that, which contains a “data” property, which we use to store our custom data.  The ITEM properties tell us the type of list we belong to and provides the “next” property, which is used to chain to the next item in the list.

 

Creating the generic LIST* and ITEM* data types help us generalize the data so that the basic processes can work with these data types, instead of having to always change the all our code to work with more specific data types, such as CHARACTER* and ANIMAL*.

We were able to hide CHARACTER* and ANIMAL* away inside ITEM* until we need to work with this specific data.  The LIST* also allowed us to protect our all import “start” and “end” properties that were previous exposed to the rest of the world to be stomped on in the previous tutorial. 

 

We adjusted a number of basic list processing functions to take and process the LIST* and ITEM* data types.  We also showed how to create a function pointer that can be used to pass custom processing into a function, so the function can me used to process different types of data.  This is a very powerful technique for letting general processing take place, yet injecting custom processing where needed in the process.  We showed that this was particularly useful when wanting to compare very specific items, while looping through the list of generic ITEM* items.

 

Now, let’s finally investigate the prospects of splitting a list, now that we’ve developed our list management code base!

 

 

Making the Split

 

Let’s take a high level look at what’s needed to split a list.  First, we have to determine where the split will be made.  Then, to split the list, we need to create a new end for the first half of the list.  Finally, we must find a way to make the second list a whole new list.  Finding where to split the list and identifying the new end of the first list are not really much of a problem.  However, making the second half of the list into a whole new list is a different story.  This is what prompted us to create a more mature list management API, using a new LIST* data type.  If we were to use the code from the first tutorial, we didn’t really have a way to represent a list.  All we knew is where a list began and where it ended.  Now, with the new functions we’ve created, along with the LIST* structure, we can easily handle the creation of a new list during a split of a list.

 

Before diving into the code, there are some special cases we need to be concerned with.  If we ask a the list splitting function to split at the starting item, what should we do?  Likewise, if the list splitting function is asked to split at the end item, what should we do?  One might feel that these are illegal requests.  It depends on ones perspective, or convention, of where the list is split, based on the request; is the item passed into the request the end of the first half of the list, or the start of the new one?  Should the request to split on the start or end item of a list be handled differently than requests to split a list at the other locations?  Here’s my take on the subject.

 

If the first item is used in the request to split, that first item will be the sole item in the first list, while the remainder is consumed by the new second list.  If the last item is used for the request to split, then that item will become the sole item of the second list, with all the preceding items remaining in their original list.  In essence, if any item other than the first is selected, then it becomes the first item of the second list.  Making these decisions are important to know what to test for in the split function we create and we’ve gotten them out of the way.

 

LIST* splitList(LIST* list, ITEM* item )

{

       LIST* newList;

       ITEM* current;

 

       if ( list != NULL && item != NULL)

       {

              // If splitting at 1st item...

              if ( list.start == item )

              {

                     newList = createList( list.listType );

                     newList.start = (list.start).next;

                     newList.end = list.end;

                     // Set new end of original list (1 item; end = start)

                     (list.start).next = NULL;

                     list.end = list.start;

                     return newList;

              } else {

                     current = list.start;

                     while( current != NULL )

                     {

                           // if item before split item...

                           if ( current.next == item )

                           {

                                  // Get first item of new list

                                  newList = createList( list.listType );

                                  // Set new list's start and end

                                  newList.start = current.next;

                                  newList.end = list.end;

                                  // Change original list's end

                                  list.end = current;

                                  current.next=NULL;

                                  return newList;

                           }

                           current = current.next;

                     }

                     return NULL;

              }

       }

       return NULL;

}

 

First, to match the “start or other item” conditions, we’ve partitioned our code of with “if” statements accordingly.  If we don’t have a valid list or item, the function returns NULL.  If the item is the “start” of the list, we search for the item.  If it’s not found, the function returns NULL.  Now, let’s talk about the processing for the positive case scenarios where the list is valid and the item will be found.  Generally speaking, the processing involved is a juggling session to rearrange the existing data to fit the condition of our new lists.

 

First, let’s look at the processing for the target item for the split being the “start” of the original list.  In this case, we call our createList() function to get a new list.  Then, we know that start’s “next” is the first item of our new second list,  so we use (list.start).next to set “newStart”, in preparation to set the “start” for newly created list.  We then call our createList() function, passing in the current list’s “type”.  Once the list is created, we set its “start” to “newStart” that we gathered up earlier, as well as move the original list’s “end” to  “newList.end”.  Next, we have to tie up the loose ends with the first half of the list.  Since there are no longer any other items in the list, we need to set “(list.start).next” equal to NULL.  Finally, we reset the original list’s “end” to “list.start” (“start” and “end” are the same since there is only one item left in the original list.

 

In the second case scenario, the item is not the start of the original list, so we must search for it.  However, we need to look ahead for the item, so when we find it, we know this item is the new last item of the first half of the list, as well as the start of the new list.  We first save off our second list’s start into “newStart” using “current.next”.  Once again, we create a new list of the same type using createList().  Once this is done we set its “start” and “end” properties.  Finally, we tie up the loose ends with the original list, setting its end to “current” and “current.next” to NULL, to disconnect it from the second half of the list we just split off.

 

 

Key Points

 

Virtually everything we’ve worked with in this exercise were pointers… address to data, no the data itself.  There is a big difference!  For instance, when we compacted our LIST* array, we were not moving whole lists from one location in the array to another, but only moving the addresses to the existing lists.

 

An important point to make about the LIST* array is you cannot treat it like it already contains LIST items only after declaring the array:

 

LIST* gLists[10];

gLists[0].start;     // CRASH!

 

You must initialize locations with existing global LIST* items or allocate memory to them using malloc() prior to attempting to access items:

 

LIST* gLists[10];

gLists[0] = malloc( sizeof(LIST) );

gLists[0].type = CHARACTER_TYPE;

 

// - or –

 

LIST* list = {type = CHARACTER_TYPE,…};

gLists[0] = list;

int type = gLists[0].type;

 

Once again, the LIST* array only holds addresses to existing LIST data, not LIST data.  This is a very common mistake!

 

In both, the first tutorial and this one, all the list management routines are juggling addresses to data, not the data itself.

 

Another thing to notice in these tutorials is the ability to identify your data types when declaring function prototypes, and the functions themselves.  If your are familiar with CScript, you most often simply pass your data using the “var” type.  Once again, CScript also does not allow you to create your own data types.  The ability to use and identify data types is helpful when trying to remember what your code is attempting do do. 

 

 

Conclusion

 

First off, as I stated in the first tutorial, I am not by any means saying that all your list processing needs to use linked lists, nor would I say you need to use the method shown here to manage linked lists.  Many applications require a simpler approach.  You might have a small list of items that won’t need to shrink or grow.  In such case, you wouldn’t need a linked list.  On the other hand, in a complex application where lists will need to shrink, grow and split, you might consider some of the techniques I’ve provided here as some of your options.  This tutorial is meant to be a learning experience, providing alternative ways of looking at processing and learning new techniques.  If I’ve achieved that, then I’m happy.  In fact, you could easily split out the code from the original tutorial into another “include” file (without the “main” function), and this code might serve its own purpose at certain times.  The same holds true for the code I’ve presented here… place it in an include file and use it when you see fit.  I’ll only warn you that some of the function names might be the same, so you might have to be made unique to coexist with one another if both are included into your program.

 

The main goal of this tutorial was to learn how to split a link list into two separate lists.  However, we determined that we needed to “beef up” our original linked list processing functions from the first tutorial.  We spent most of our time discussing ways to make our new processing more flexible, for minimal changes in any future works.  In the process, we even re-factored the new code out into some functions to minimize the number of top level function calls we’d have to call from the linked list API we created.  Once again we used features that are found under LiteC, that aren’t available in CScript, which are very similar to the C language.

 

Just as I stated in the first tutorial, if you are interested in moving on to writing your gaming code in C, then you may want to change some of the syntax we used in the code.  The main concern is the syntax used to access the member of our structures (LIST, ITEM, CHARACTER and ANIMAL).  LiteC always allows the “.” operator.  In contract, were are working with structure pointers, where C requires “->” instead of the “.” operator.  LiteC accepts the “->” in these situations, so using this operator will make your code more portable to the C language.

 

As stated earlier in this paper, there are areas we didn’t cover.  There are a good number of functions you might want to add to the collection we started to create; deleteFirst(), deleteLast(), deleteByValue(), prependToList(), appendToList(), joinLists(), etc.  So, there are a good number of functions that you could add to the work presented here to meet your processing needs.  Feel free to experiment and learn.  Don’t be afraid to mess up, because that’s how we learn what NOT to do!

 

Happy trails!  A full code listing follows for your study.

 

 

MULTILIST.C LISTING

 

#include <acknex.h>

#include <default.c>

 

// SPECIAL DEFINES //////////////////////////////////////////

#define CHARACTER_TYPE     0

#define ANIMAL_TYPE        1

#define MAX_LISTS          10

 

// IMPORTED FUNCTIONS ///////////////////////////////////////

int strcmp(char* a, char* b);           

char* strcpy(char* a, char* b);

 

strcpy = DefineApi("mscvrt!strcpy");

strcmp = DefineApi("mscvrt!strcmp");

 

// TYPEDEFS /////////////////////////////////////////////////

typedef struct character

{

   char letter[2];

} CHARACTER;

 

typedef struct animal

{

   char name[30];

} ANIMAL;

 

typedef struct item

{     

       int listType;

       void* data;                //... holds pointer to any struct that holds your custom data

       struct item* next;//    so must cast to your struct* type when you retrieve "data"

} ITEM;

 

typedef struct list{

       int listType;

       int position;

       ITEM* start;

       ITEM* end;

       int count;

} LIST;

 

// FUNCTION PROTOTYPES /////////////////////////////////////

int compareString( ITEM* item, char* value );   // prototype/function pointer

 

// GLOBALS /////////////////////////////////////////////////

LIST* gLists[ MAX_LISTS ];

int gListCount = 0;

 

// FUNCTIONS ///////////////////////////////////////////////

void initializeLists( )

{

       int i;

       LIST* current;

       for(i = 0; i < MAX_LISTS;i++)

       {

              gLists[i] = NULL;

       }

       gListCount = 0;

}

 

 

int characterCompare(ITEM* item, char* value)

{

       CHARACTER* charItem;

       charItem = (CHARACTER*) item.data;

       return ( strcmp(charItem.letter, value) == 0 );

}

 

int animalCompare(ITEM* item, char* value)

{

       ANIMAL* animalItem;

       animalItem = (ANIMAL*) item.data;

       return ( strcmp(animalItem.name, value) == 0 );

}

 

LIST* createList(int listType)

{

       if ( gListCount < MAX_LISTS )

       {

              if ( listType ==  CHARACTER_TYPE  || listType == ANIMAL_TYPE )

              {

                     gLists[gListCount] = malloc( sizeof(LIST));

                     if ( gLists[gListCount] != NULL)

                     {

                           gLists[gListCount].start = NULL;

                           gLists[gListCount].end = NULL;

                           gLists[gListCount].position = gListCount;

                           gLists[gListCount].listType = listType;

                           gListCount++;

                           return gLists[gListCount-1];

                     }

              }

       }

       return NULL;

}

 

void freeList(LIST* list)

{

       ITEM* current;

       void* data;

       current = list.start;

 

       while( current != NULL)

       {

              list.start = current.next;

              data = current.data;

              free(data);

              free(current);

              current = list.start;

       }

}

 

void deleteList( LIST* list )

{

       int startIdx;

       int i;

       if ( list != NULL )

       {

              startIdx = list.position;

              freeList( list );

              free( list );

              gLists[startIdx] = NULL;

              for( i = startIdx; i < gListCount;i++ )

              {

                     if ( gLists[i+1] == NULL) break;

                     gLists[i] = gLists[i+1];

                     gLists[i].position = i;

                     gLists[i+1] = NULL; 

              }

              --gListCount;

              if ( gListCount < 0)

                     gListCount = 0;

              return 1;

       }

       return 0;

}

 

ITEM* createItem( int listType )

{

       ITEM* tempItem;

       CHARACTER* tempCharacter;

       ANIMAL* tempAnimal;

      

       tempItem = malloc( sizeof(ITEM) );

       tempItem.next = NULL;

 

       if (listType == CHARACTER_TYPE )

       {

              tempItem.listType = CHARACTER_TYPE;

              tempCharacter = malloc( sizeof (CHARACTER) );

              tempItem.data = (void*)tempCharacter;

       }      else {

              if (listType == ANIMAL_TYPE )

              {

                     tempItem.listType = ANIMAL_TYPE;

                     tempAnimal = malloc( sizeof (ANIMAL) );

                     tempItem.data = (void*)tempAnimal;

              } else {

                     free(tempItem);

                     return NULL;

              }

       }

       return tempItem;

}

 

ITEM* populateAnimal( ITEM* item, char* name )

{

       ANIMAL* tempAnimal;

       if( item != NULL && name != NULL )

       {

              if ( item.listType == ANIMAL_TYPE )

              {

                     tempAnimal = (ANIMAL*) item.data;

                     strcpy( tempAnimal.name, name );

                     return item;

              }

              // else, ERROR: item is not of the type we are asking to populate.

       }

       // else, ERROR: item and/or passed in data are NULL

       return NULL;

}

 

ITEM* populateCharacter( ITEM* item, char* letter )

{

       CHARACTER* tempCharacter;

      

       if( item != NULL && letter != NULL )

       {

              if ( item.listType == CHARACTER_TYPE )

              {

                     tempCharacter = (CHARACTER*) item.data;

                     strcpy( tempCharacter.letter, letter );

                     return item;

              }

       }

       // else, ERROR: item and/or passed in data are NULL

       return NULL;

}

 

ITEM* createCharacterItem( char* letter)

{

       CHARACTER* tempCharacter;

      

       tempCharacter = createItem( CHARACTER_TYPE );

       if ( tempCharacter != NULL )

       {

              return populateCharacter( tempCharacter, letter );

       }

       return NULL;

}

 

ITEM* createAnimalItem( char* name)

{

       ANIMAL* tempAnimal;

       tempAnimal = createItem(ANIMAL_TYPE);

       if ( tempAnimal != NULL )

       {

              return populateAnimal( tempAnimal, name );

       }

       return NULL;

}

 

int addItem( LIST* list, ITEM* item)

{

       ITEM* end;

      

       if ( list != NULL && item != NULL && item.listType == list.listType )

       {

              if ( list.start == NULL )  // if the list is empty...

              {

                     list.start = item;

              } else {

                     end = list.end;

                     end.next = item;

              }

              list.end = item;

              return 1;

       }

       return 0;

}

 

ITEM* findItem( LIST* list, char* value, void* functPtr)

{

       ITEM *current;

       current = list.start;

 

       compareString = functPtr;

       while( current != NULL )

       {

              if ( compareString(current, value) )

                     return current;

              current = current.next;

       }

       return NULL;

}

 

void destroyLists( )

{

       LIST* current;

       current = gLists[0];

       while( current != NULL)

       {

              deleteList( current );

              current = gLists[0]; 

       //... deleteList() moves any next remaining list

       //    addresses down, refilling this position

       }

}

 

LIST* splitList(LIST* list, ITEM* item )

{

       LIST* newList;

       ITEM* current;

 

       if ( list != NULL && item != NULL)

       {

              // If splitting at 1st item...

              if ( list.start == item )

              {

                     newList = createList( list.listType );

                     newList.start = (list.start).next;

                     newList.end = list.end;

                     // Set new end of original list (1 item; end = start)

                     (list.start).next = NULL;

                     list.end = list.start;

                     return newList;

              } else {

                     current = list.start;

                     while( current != NULL )

                     {

                           // if item before split item...

                           if ( current.next == item )

                           {

                                  // Get first item of new list

                                  newList = createList( list.listType );

                                  // Set new list's start and end

                                  newList.start = current.next;

                                  newList.end = list.end;

                                  // Change original list's end

                                  list.end = current;

                                  current.next=NULL;

                                  return newList;

                           }

                           current = current.next;

                     }

                     return NULL;

              }

       }

       return NULL;

}

 

// MAIN //////////////////////////////////////////////////////////

void main()

{

       // For CHARACTER work...

       LIST* characterList;

       ITEM* characterItem;

      

       // For ANIMAL work...

       LIST* animalList;

       ITEM* animalItem;

      

       // For new list after split...

       LIST* newList;

      

       // !!! MUST INITIALIZE LISTS TO NOTHING !!!

       initializeLists();

      

      

       // Build list of CHARACTERs...

       characterList = createList( CHARACTER_TYPE );

       characterItem = createCharacterItem("A");

       addItem(characterList, characterItem );

       characterItem = createCharacterItem("B");

       addItem(characterList, characterItem );

       characterItem = createCharacterItem("C");

       addItem(characterList, characterItem );

      

       // Build list of ANIMALs...

       animalList = createList( ANIMAL_TYPE );

       animalItem = createAnimalItem("dog");

       addItem(animalList, animalItem );

       animalItem = createAnimalItem("cat");

       addItem(animalList, animalItem );

       animalItem = createAnimalItem("tiger");

       addItem(animalList, animalItem );

      

       // TEST: FIND "B" IN CHARACTERS...

       characterItem =  findItem(characterList, "B",  characterCompare );

       printf("%s",((CHARACTER*)characterItem.data).letter); 

 

       // TEST: FIND "cat" IN ANIMALS...

       animalItem =  findItem(animalList, "cat",  animalCompare );

       printf("%s",((ANIMAL*)animalItem.data).name);

      

       // TEST LIST SPLIT - Should find "B" in new list...

       newList= splitList(characterList, characterItem);

       characterItem =  findItem(newList, "B",  characterCompare );

       printf("%s",((CHARACTER*)characterItem.data).letter); 

      

       // ... "B" (and "C") no long in orig. characterList; findItem() = NULL

       characterItem =  findItem(characterList, "B",  characterCompare );

       if (characterItem == NULL)

              printf("B no longer in original characterList");      

       characterItem =  findItem(characterList, "C",  characterCompare );

       if (characterItem == NULL)

              printf("C no longer in original characterList");      

 

       // !!! MUST CLEAN UP MEMORY ALLOCATED TO LIST(S) !!!

       destroyLists();     

}