LIBLIST List Manipulation Library

CONTENTS

  • Introduction
  • Using this library
  • List Classes
  • Ordered and Unordered lists
  • Data Types
  • Properties
  • Methods
  • User supplied functions
  • Example program
  • Author and Copyright
  • Release History

  • * INTRODUCTION

    When programming in C and you intend to write any type of complex program then you often find yourself wanting to manipulate lists of data. You therefore find yourself implementing list handling routines in each such program. This is costly in time and prone to error. Additionally the main logic of your program is often obscurred by the code which does the mechanics of the list handling.

    This library simplifies this problem by providing a packaged series of list routines that are known to work. The details of the list manipulation are no longer your concern - all you need to do is to specify the actions that you require. All details of the internal control structures used to maintain such lists are hidden from you. Instead you are just provided with a series of properties and methods that you can use against any list type.

    The library treats lists in an object oriented manner which will allow you to clearly separate out those bits of any program that are associated with list handling. This leads to better structured source code that is much easier to follow. A consequence is that your program is less prone to logic errors.

    A variety of different types of list are supported. Each type has different benefits in terms of cost, storage, etc. The same methods and properties are supported for each list type as far as is practical, even though for some of the types this can be relatively inefficient in processing terms. This means that you can pick the list type that is optimised for your purposes, but if later your requirements change you can change the list type used without major change to your program. The new list type will support the same properties and methods that you have used on the previous list type.

    Once you have mastered using this library, then you will find that many problems that manipulate lists become much easier to solve. You no longer have to think of the complexities of how lists operate, but just think of what you want to use them for.


    * USING THIS LIBRARY

    When you get this library, then you should have the following files provided:

            LIBLIST.HTM     Documentation in HTML format
    
            LIBLIST.H       Header file for use with library (packed version)
            LIBLIST.HDR     Header file for use with library (annotated version)
            LIBLIST.A       Standard version of library
    
            LIBLISTD.A      Debug version of library
            LISTSRC.ZIP     Source of the LIBLIST library as a ZIP archive
    

    The first thing is that in your source files you must include the header file supplied with this library in any module that is going to make use of any of the methods or properties that are part of this library. This is done by including a line of the form

          #include <liblist.h>
    
    in your source at an appropriate point.

    The second point is that the library is provided in a number of different formats:

    Standard Version
    This is a normal statically linked version. If you want to use this version then you need to provide a parameter of the form
            -llist
    
    to the LD linker.

    Debug Version
    Programs that make extensive use of lists are often by their very nature relatively complex and thus more prone to having internal logic faults.

    The debugging version of this library helps identify such problems by including extensive internal checks against you misusing the library and providing silly parameter values. This is extremely useful while in the development phase of a program. The penalty is that there is significantly more overhead in terms of program size, memory usage and processing overhead while using the debugging version of the library.

    To use the debugging version of the library you need to provide a parameter of the form

            -llistd
    
    to the LD linker.

    While you are using the debugging version of the library, if any of the internal checks fail then your program will be stopped and an assert style message displayed indicating the point at which the error was detected. The text of the assert message will display the details of the internal check that failed. These messages are designed to be self-explanatory.

    Once you have finished debugging your program, then by simply changing the parameter provided to the LD linker you can switch to a non-debugging version of the library.

    RLL Version
    The RLL (Runtime Link Library) version is provided to allow the required routines to be picked up dynamically at runtime.

    To use the RLL version of the library you need to provide parameters of the form

            -rlist -llist
    
    to the LD linker. Note that both the above parameters are needed as there are still a few routines (at this release anyway) that still need to be picked up from the statically linked version of the library.

    Note also that if you use this latter format it is necessary to ensure that the user has a copy of the RLL library file LIBLIST_RLL as this will be required at runtime. The user will also need to have the Runtime Link Manager (or RLM) present and running on his system at the time that your program that uses a RLL file is started.

    These variants of this library are functionally identical as far as the methods and properties supported are concerned.

    This means that you can develop your program using the debugging variant of the library. When you have finished debugging your program, then you can simply change the parameter to the LD linker, and relink your program with either the standard version or the RLL version to get a reduction in program size, runtime memory usage and an increase in speed.


    * LIST CLASSES

    The following are the list classes currently supported by this library (more may be added in future releases). Although the use of this library largely hides the details of such complexity from you, you should still be aware of the characteristics of each list class so that you can select the class that is optimised for the purpose that you have in mind.

    Single Linked List
    This one of the commonest types of list. It is typically used to store items that are unordered, although ordered operations are still supported at the expense of additional processing overheads.

    The advantages of this type of list are that it has the minimal amount of memory overhead associated with storing each entry (or node) in the list.

    The disadvantages of this type of list are that operations that involve add new entries towards the end of the list; searching for particular entries in the list; or attempting to move backwards through the list tend to be rather expensive in processing terms, and the overhead tends to grow linearly with list size.

    Double Linked list
    This is a convenient way of storing a small amount of data in either ordered or unordered form.

    It is similar in many ways to the Single Linked list mentioned above, except that it also supports reasonably efficient movement backwards through the list. The penalty you pay is slightly more memory overhead for each node in the list.

    Queues (FIFO)
    A queue is a specialised use of a list where you always want to add items at the end and remove them from the front. This is why it is also commonly known as a FIFO or First-In-First-Out list.

    A queue is optimised for inserting new nodes at the end of the list and removing old nodes from the front. It also does not support the concept of an ordered list. In other respects it tends to behave rather like the Single Linked List mentioned earlier.

    Stacks (LIFO)
    A stack is used when you always want to add items at the end of the list and remove them from the front. This is why it is also commonly known as a LIFO or Last-In-First-Out list.

    A stack is optimised for inserting and removing new nodes from the front of the list. It also does not support the concept of an ordered ordered list. In all other respects it tends to behave rather like the Single Linked List mentioned earlier.

    Binary Tree
    A binary tree is commonly used when you want to store large numbers of items that need to be kept in some sort of ordered manner. A typical example might be a list of program symbols.

    The advantage of the binary tree is that it is optimised for quick searching and insertion of new nodes with the minimum of processing overheads.

    The disadvantage of the binary tree is that the storage overhead is slightly more than for any of the linked list types mentioned earlier. It is also a very inefficient way to store unordered data - for this it is better to use one of the linked list variants.

    Balanced Binary Tree
    **** Not yet ready for use ****
    A balanced binary tree is a specialisation of the standard binary tree type that tries to optimise the search operations at the expense of more processing overhead being incurred during the data insertion methods.

    In functional terms it should be identical to the standard binary tree type mentioned earlier.

    Hash Table
    **** Not yet ready for use ****
    A hash table is used in situations in which the key requirement is very fast look-up of whether nodes already exist or not. It does not by its very nature support the idea of an ordered list. A typical use might be in a compiler for storing information about symbols as one would continually be checking to see if a particular symbol exists.

    The idea of a hashtable is that is initialised with a specified number of nodes (this number is then fixed for the duration of this particular instance of the hash table). One then uses a routine (called the hash algorithm) that does some sort of calculation on the supplied data to determine in which node (or bucket as it is commonly known) this data should be stored. As this does not involve any searches, and as the calculations can be optimised for speed and all the required nodes are pre-created, this process can very fast.

    In practise a realistic use of a hash table is almost always implemented as a 'list of lists'. The first level is the true hash table which uses the hashing algorithm to get down to a particular bucket within the hash table, and then attached to that bucket is some other sort of list to resolve the situation of there being more than one node that has the same hash value. This second level can be another level of hash table (using a different hashing algorithm to the first level) or some other type of list. In many cases the last level is a LIFO (or stack) type of list as this means that it can store any amount of data (memory permitting) and the most recently inserted node (which is the one you are often most likely to want) is found first in any search.


    * DATA TYPES

    There are a number of specific data types that are used throughout this library.

    list_t
    This is a generic pointer data type returned when a list is created. It is effectively the handle to any particular list operation, and is passed as a parameter to all such operations. It is the use of this technique that allows this library to insulate the programmer from the implementation details of each of the list types.

    listclass_t
    This is a data type that is passed as a parameter when creating a list. It defines which of the classes of lists given earlier should be used when creating this new list instance. This will be the only use made of this data type at the user level, although it is widely used internally within the implementation of this library.

    node_t
    This is a generic 'pointer to node' type that is passed as a parameter to the various library routines, and in some cases returned as an address from these routines. The important thing about it is that it always points at the user data part of any node - the programmer does not see any associated control information.

    Within user code one would normally cast this generic type to a pointer to a particular structure type. This will allow you to access the fields that are stored within the node. For typical usage see the example program later on in this document.




    * ORDERED and UNORDERED LISTS

    Within the LIBLIST library we have the concept of ordered and unordered lists.

    ordered lists
    In an ordered list one (or more) of the fields within a list node are designated as key fields. The position that an entry should occupy in the list is determined by comparing the key fields of the various entries. When using an ordered list you normally just tell the system to add a new entry to the list and let it work out the correct position that it should occupy by doing any needed comparisons on the designated key fields. A typical use of an ordered list might be for a list of names that you want maintained in alphabetical order.

    If you want to use order lists, then you have to supply a Node Comparison function at the time you create the list that is used by the library to decide the correct order. This is described in more detail later in this document in the section on user defined functions. Note also that not all the list types can support the concept of an ordered list. The description of each list type will tell you whether ordered lists are supported or not.

    unordered lists
    In an unordered list on the other hand there is no explicit key field. Instead the order is simply determined by the locations at which you insert new entries. When building an unordered list you have to specify exactly where any new entry should be inserted. This mode of operation is particularly suited to lists where you will be inserting and/or deleting entries from either the front or the back of the list.

    It is also worth noting that there are times when the concept of whether a list is ordered or not does not really make sense. This is the case of the Hash Table class of list (where the hash function is always invoked to determine what bucket data is associated with).

    * PROPERTIES

    The properties are used to obtain information about the current list. They do not change the list in any way, but just give information about the list. To be able to obtain the properties of any list you must (obviously) have earlier created one.

    The LIBLIST library actually supports all properties for all list types. However each list type has a selection of the properties that are most frequently used with that type. Use of other properties is likely in many cases to be relatively expensive in processing terms.

    The following tables show the properties that you are most likely to use with each list type, and then for each property (in alphabetical order) a detailed description is given describing its syntax and use.

    Commonly used Properties



    Properties
    List Types
    Single
    Linked
    List
    Double
    Linked
    List
    FIFO
    List
    (Queue)
    LIFO
    List
    (Stack)
    Binary
    Tree
    Balanced
    Binary
    Tree
    Hash
    Table
    LIST_Ordered yes yes yes yes yes yes n/a
    LIST_Count yes yes yes yes yes yes yes
    LIST_Index yes yes yes rarely yes yes -
    LIST_First yes yes - - - - -
    LIST_Last yes yes - - - - -
    LIST_Next yes yes - - rarely rarely rarely
    LIST_Previous yes yes - - rarely rarely rarely


    int   LIST_Count(list_t list);
    
    Returns a count of the number of items in the list. A value of 0 means that the list is empty.
    node_t LIST_First(list_t list);
    
    Returns a pointer to the first node in the list, or NULL if the list is empty.

    This property would normally only be used on unordered lists. On ordered lists you are more likely to be using the LIST_Find or LIST_Enumerate methods.


    int   LIST_Index(list_t list, node_t node);
    
    Returns the index of the specified node in the list, or 0 if the node cannot be found in the list (or if the list is empty). If you later want to retrieve a pointer to the node then you can use the LIST_Position method to obtain a pointer to the node with a given index value.

    One point to watch though is the index value may not stay valid if you insert new data into a list. Whether it does or not will depend on the relative positions in the list at which the new data is inserted. You should always assume that inserting new data invalidates any current index property that you may have obtained earlier.


    node_t LIST_Last(list_t list);
    
    Returns a pointer to the last node in the list, or NULL if the list is empty.

    This property would normally only be used on unordered lists. On ordered lists you are more likely to be using the LIST_Find or LIST_Enumerate methods.


    node_t  LIST_Next(list_t list, node_t node);
    
    Returns a pointer to the next node in the list, or NULL if the end of the list has been reached.
    int   LIST_Ordered(list_t);
    
    Used to determine whether a list is ordered or not. The values that can be returned are: LIST_ORDERED List is ordered LIST_UNORDERED List is unordered LIST_ORDER_NA Order not applicable to this list type

    In practise you would rarely need to use this property as you had to specify when you created the list whether it was ordered or not.


    node_t LIST_Previous(list_t list, node_t node);
    
    Returns a pointer to the previous node in the list, or NULL if the start of the list has been reached.


    * METHODS

    The methods are used to carry out some action on the list. In most cases they will actually change the contents of the list.

    The LIBLIST library actually supports all methods for all list types. However each list type has a selection of methods that are most frequently used with that type.

    The following table show the methods that you are most likely to use with each list type, and then for each method (in alphabetical order) a detailed description is given describing its syntax and use.

    Commonly used Methods



    Methods
    List Types
    Single
    Linked
    List
    Double
    Linked
    List
    FIFO
    List
    (Queue)
    LIFO
    List
    (Stack)
    Binary
    Tree
    Balanced
    Binary
    Tree
    Hash
    Table
    LIST_Create yes yes yes yes yes yes yes
    LIST_Clone yes yes yes yes yes yes yes
    LIST_Hash error error error error error error yes
    LIST_Destroy yes yes yes yes yes yes yes
    LIST_NewNode yes yes yes yes yes yes yes
    LIST_FreeNode yes yes yes yes yes yes yes
    LIST_Add yes yes - - yes yes yes
    LIST_NewAdd yes yes - - yes yes yes
    LIST_Insert yes yes - - - - -
    LIST_NewInsert yes yes - - - - -
    LIST_NewAppend yes yes - - - - -
    LIST_Append yes yes - - - - -
    LIST_Before yes yes rarely rarely - - -
    LIST_NewBefore yes yes rarely rarely - - -
    LIST_After yes yes rarely rarely - - -
    LIST_NewAfter yes yes rarely rarely - - -
    LIST_Remove yes yes rarely rarely yes yes yes
    LIST_Delete yes yes rarely rarely yes yes yes
    LIST_Delete yes yes rarely rarely yes yes yes
    LIST_Position yes yes yes rarely rarely rarely rarely
    LIST_Find yes yes - - yes yes yes
    LIST_Enqueue - - yes - - - -
    LIST_Dequeue - - yes - - - -
    LIST_NewEnqueue - - yes - - - -
    LIST_Push - - - yes - - -
    LIST_NewPush - - - yes - - -
    LIST_Pop maybe maybe - yes - - -
    LIST_Peek maybe maybe yes yes - - -

    int   LIST_Add(list_t list, node_t node);
    
    This method adds a node to the list. It will return 0 on success, and an error code on failure.

    It is normally expected to be used with ordered lists, and in this case the new node will be inserted at the correct point to maintain the order. If used with an unordered list, then the new node will be treated as if it were an LIST_Append method and the node added at the end of the list.


    int   LIST_After(list_t list, node_t newnode, node_t oldnode);
    
    This method adds a node after the given node. It will return 0 on success, and an error code on failure.

    It is normally expected to be used with unordered lists. If used with ordered lists, then it is an error if inserting the specified node at the specified point would violate the ordering constraints.

    If the oldnode parameter is NULL, then this method will act just like the LIST_Append method and insert the new node as the last node in the list.


    int   LIST_Append(list_t list, node_t node);
    
    This method inserts the given node at the end of the list. It will return 0 on success, and an error code on failure.

    It is normally expected to be used with unordered lists. If used with ordered lists, then it is an error if inserting the specified node at the end of the list would violate the ordering constraints.


    int   LIST_Before(list_t list, node_t newnode, node_t oldnode);
    
    This method adds a node before the given node. It will return 0 on success, and an error code on failure.

    It is normally expected to be used with unordered lists. If used with ordered lists, then it is an error if inserting the specified node at the specified point would violate the ordering constraints.

    If the oldnode parameter is NULL, then this will act just like the LIST_Insert method and insert the new node as the first node in the list.


    list_t LIST_Clone(list_t oldlist);
    
    This method will make a clone of the specified list. By this we mean another empty list that is similar in all respects to the one given in the list parameter. In other words as if you had used the LIST_Create method with exactly the same parameters as were used to create the oldlist list specified as the parameter. It is perfectly legal to use the LIST_Clone method on a list instance that is itself a clone - you do not always have to use the same instance as was specified in the original LIST_Create method call.

    The value returned is a handle to the new list instance if the method was successful. If for any reason an error occurred, then NULL will be returned and the global error variable errno set to indicate the type of error that occurred. In practise as long the oldlist parameter was valid probably the only error that will ever occur is ENOMEM because you have run out of memory.

    The fundemental difference between using the LIST_Create and LIST_Clone method calls is that with the LIST_Clone method both the new and the old list will share information about the list attributes such as its node size, and the user supplied functions for node initialisation, node destruction and node comparison. This means that there is significantly less memory overhead when the LIST_Clone method is used compared to when another LIST_Create method call is used. This is particularly important for situations in which you may have a very large number of occurrences of a particular list type.

    An example of when this situation might arise is if you have a list, and then to each node of that list you are going to attach further lists. In this case it is much more effecient in memory terms to issue a single LIST_Create method call to create the first instance of the attached list, and then use. LIST_Clone method calls to create all the rest of the instances. In fact you may well issue a dummy LIST_Create method call as part of your program initialisation to create the first instance which you will never destroy, and then simply use LIST_Clone method calls as appropriate to create further instances of this type of list.


    int  LIST_Compare(list_t list, node_t node1, node_t node2)
    
    This method is used to compare two nodes.

    If this is an ordered list (the user supplied a Node Compare function when the list was created) it will return values as defined for the Node Compare function.

    If this is an unordered list then the comparison will be based on the physical order in the list.


    list_t LIST_Create (list_t type, size_t node_size,
                         int (*initnode)(node_t node, va_list params),
                         int (*killnode)(node_t),
                         int (*compnode)(node_t,node_t);
    
    This is used to create a new list. On success it returns a pointer to the list handle which will be used in all subsequent methods and property calls associated with this list. On failure NULL is returned and the global variable errno set to indicate the cause of the failure.

    The type parameter can be any one of the following constants that are defined in the liblist.h header file:

           LIST_SINGLE           Singly linked list
           LIST_DOUBLE           Doubly linked list
           LIST_QUEUE            Queue
           LIST_FIFO             FIFO (another name for queue)
           LIST_STACK            Stack
           LIST_LIFO             LIFO (another name for stack)
           LIST_BTREE            Binary tree
           LIST_BALTREE          Balanced Binary Tree
           LIST_HASH             Hash Table
    
    The user can optionally provide the addresses of three user supplied functions.

    More detail on these User Supplied Functions is given later in this document.


    int  LIST_Delete(list_t list, node_t node);
    
    This method will remove the node from the list and then free it. It will return 0 on success, and an error code on failure.

    It is effectively a combination of the LIST_Remove method to remove the node from the list, followed immediately by a LIST_FreeNode method on the same node.


    node_t  LIST_Dequeue(list_t list);
    
    This method is provided as the standard way to remove a node from a list that is acting as a queue.

    It will remove the first node from the given list and return a pointer to node. If the list is empty, then NULL will be returned.

    The implementation of this method means that it is functionally equivalent to writing LIST_Remove(LIST_First(list))


    int   LIST_Destroy(list_t list);
    
    This is used when a list is finished with to destroy a list, releasing all the associated memory.

    First each node in turn will be destroyed using the LIST_Delete method, and then when all the resources associated with the nodes have been freed, the list handle itself will be destroyed. If this is the last instance of a cloned list, then the resources describing the information that is common between clones will also be released.


    int   LIST_Enqueue(list_t list, node_t node);
    
    This routine is provided as the standard way to add nodes to a list that is acting as a queue. It will return 0 on success, and an error code on failure.

    The implementation of this method means it will insert a given node at the end of the list, and it is thus functionally equivalent to the LIST_Append method.


    long  LIST_Enumerate(list_t list, (*func)(node_t, va_list), ...);
    
    This function will work through each node in turn of a list, and for each node call the user supplied node enumeration function. If at any node this function returns a non-zero value, then the enumeration process will be terminated early and the return value from the user function returned as the result of this method. If the function returns 0 to every invocation, then every node in the list will be enumerated and the method will return 0.

    The enumeration will proceed according to whatever ordering criteria have been specified. A typical use of this function is when you want to perform an operation on every node in the list, such as for instance listing out its details.

    More details on the func parameter is given in the section on user defined functions under the Node Enumeration functions topic.

    If any additional parameters are supplied following the name of the user defined function, then these will be passed to the function every time it is invoked using the methods defined in the stdarg.h header file. However some care needs to be taken within the user defined function on the use of these parameters as they are not reset between invocations for each node. This means that they must not be changed in any way unless you want the next invocation of the function to inherit the changes.

    Note that the return type of this method is long. This is so that the user supplied function can, if desired, pass back a pointer as a result code. The ISO C standard says that that the programmer is entitled to assume that the long data type can be freely cast to/from any pointer data type.


    node_t  LIST_Find (list_t, int (*findfunc)(node_t,va_list), ...);
    
    This function is used to find a particular node from within a list.

    You need to provide the name of the findfunc function that is to be called for each node (to determine whether it is the one that you want or not. If any parameters follow the name of the function, then they will be passed to the function each time it is invoked using the methods defined in the stdarg.h header file. More detail on how this function should operate is given under the Node Find function description in the User Supplied functions section later in this document.


    int   LIST_FreeNode(list_t list, node_t node);
    
    This method is used to destroy a node that is not currently linked into a list. The user supplied Node Destruction function (if any) will be called when the node is freed. The method will return 0 on success, and an error code on failure.

    It is an error to call this function for a node which is still linked into the list. If you want to destroy a node that is still linked into a list you should use the LIST_Destroy method instead.


    int   LIST_HashSetup(list_t list,
                         size_t, bucket_count,
                         size_t  (*HashCalc)(va_list),
                         list_t  LinkedList);
    
    This method is specific to Hash Tables, and will return an error if used on any other list type. It is used to specify attributes that are specific to the hash table type of list. It must be used after you have used a LIST_Create or a LIST_Clone method call to create an instance of the list, but before you have inserted any data into the list.

    The parameters are used as follows:


    int   LIST_Insert(list_t list, node_t node);
    
    This method inserts the given node at start of the list. It will return 0 on success, and an error code on failure.

    It is normally expected to be used with unordered lists. If used with ordered lists, then it is an error if inserting the specified node at the start of the list would violate the ordering constraints.


    node_t  LIST_NewNode(list_t list, ...);
    
    This method is used to create a new node. It will not be inserted anywhere in the list at this stage. The user supplied node initialisation routine (if any) supplied to the LIST_Create method call that was used to create the list will be called automatically to complete any user defined initialisation of this new node.

    This method can optionally have additional parameters, and if present they are passed to the users node initialisation routine as defined in the section on User Supplied Functions.

    On success a pointer to the new node is returned. If any error occurs, then NULL will be returned, and the global error variable errno set to indicate the error type.


    node_t  LIST_NewAdd (list_t, ...);
    node_t  LIST_NewInsert (list_t,  ...);
    node_t  LIST_NewAppend (list_t, ...);
    node_t  LIST_NewBefore (list_t, node_t oldnode, ...);
    node_t  LIST_NewAfter (list_t, node_t oldnode, ...);
    node_t  LIST_NewEnqueue (list_t, ...);
    node_t  LIST_NewPush (list_t, ...);
    
    To be able to use these functions, you MUST have provided a node initialisation function to populate the new node when you created the list via the LIST_Create method. If you did not supply the node initialisation function, then these methods will always fail with the global error variable errno set to EACCES (as defined in the errno.h header file).

    These methods are effectively a combination of the creation of a node with the LIST_NewNode method and the corresponding list insertion method. They are just provided for convenience as you frequently want to create a new node and immediately insert it into the list.

    On success these methods return a pointer to the new node. On failure they return NULL, and the errno global error variable will be set to indicate the error type. In fact the only common error is likely to be ENOMEM that would be returned if you run out of free memory.

    All these methods can optionally take additional parameters that will (if present) be passed to the user supplied node initialisation routine in the same manner as is described under the LIST_NewNode method.

    For more details read the descriptions given with the LIST_NewNode method and the various methods for inserting nodes into a list.


    node_t  LIST_Peek(list_t list);
    
    This routine is provided as the standard way to look at the node that would be returned by the LIST_Dequeue or a LIST_Pop methods without actually removing the node from the list.

    It is actually implemented as functionally equivalent to the LIST_First property, but is the term more commonly associated with handling of queues and stacks.


    node_t  LIST_Pop(list_t list);
    
    This method is provided as the standard way to remove a node from a list that is acting as a stack.

    It will remove the first node from the given list and return a pointer to that node. If the list is empty, then NULL will be returned. This routine is functionally equivalent to writing LIST_Remove (LIST_First(list))


    node_t  LIST_Position(list_t list, size_t index);
    
    This method is provided as the way to obtain a pointer to a node at a given index value in a list. The index parameter is equivalent to the value returned by the LIST_Index property. A value of NULL is returned if there is no node with the supplied value of index.


    int   LIST_Push(list_t list, node_t node);
    
    This routine is provided as the standard way to add nodes to a list that is acting as a stack. It will return 0 on success, and an error code on failure.

    It is actually implemented to insert the given node at the start of the list, so it is functionally equivalent to the LIST_Insert method.


    node_t  LIST_Remove(list_t list, node_t node);
    
    This method unlinks a node from the list without destroying the node. It can be used when you want to link the node back into the list at a different point.

    It is normally only relevant to unordered lists.


    * USER SUPPLIED FUNCTIONS

    These are functions that are supplied by the user. They are called at specified points in the processing of a list. They are what is commonly known as Callback Functions in that they are called back from within the library methods. They are called to carry out a number of different purposes:

    Node Initialisation Function

    When a new node is created, any pointer fields are automatically set to NULL, and all other data fields set to zero. This function will be used to do any additional initialisation that is required. This function has the following prototype:

            int  initnode (node_t node, va_list);
    
    although the actual name does not have to be initnode but can be any name that the user desires, and will typically be different for each list that is created. The node parameter is a pointer to the user part of the new node that has just been created. The ap parameter is a pointer to any additional parameters that might have been passed with the LIST_Enumerate method call. These parameters are accessed using the standard C library functions associated with the stdarg.h header file.

    On success this routine should return 0. Any other value will be treated as an error code, and passed back to the calling program.

    Use of this function allows for setting up of complex data types such as lists that themselves contain lists.

    If the Node Initialisation function allocates any memory to be attached to the node, then you should provide a corresponding Node Destruction function to release this memory. The only exception to this rule would be if you never use the List_Delete method to destroy individual nodes or the LIST_Destroy method to destroy a list. In this case the resources will instead be automatically released when the program terminates.


    Node Destruction Function

    When a node is to be destroyed for any reason, this function (if supplied) is called for each node. As such it is basically a companion function to the Node Initialisation function.

    The prototype of this function is:

          int   killnode (node_t node)
    
    although the actual name need not be killnode, but can be anything that the user desires, and will typically be different for each list that is created. The node parameter is a pointer to the user part of the node that is about to be destroyed.

    The normal thing that is done within this routine is the release of any resources that have been attached to this node. This might simply be malloc'ed memory that has been attached to this node, or it might be something much more significant such as destroying further lists that are attached to this node.


    Node Compare Function

    The Node Comparison function is only required if a list is to be ordered (i.e. maintained in some sort of specified order). This function is used to compare two nodes in the list to determine which one is to be considered the larger for ordering purposes.

    This function has the following prototype:

            int  comparenode (node_t node1, node_t node2);
    
    although the name does not need to be comparenode, but can be any name that the user desires, and would typically be different for each list that is created. Pointers to the users part of the two nodes to be compared are passed as the node1 and node2 parameters. The return value is interpreted (using the same convention as is used by the strcmp standard C library function) as follows:
            -ve     node1 is less than node 2
             0      node1 is equal to node 2
            +ve     node1 is greater than node 2.
    

    Node Enumeration Function(s)

    Node Enumeration functions are used whenever you decide to enumerate a list (i.e. do something with every node in turn) using the LIST_Enumerate method call. It is likely that each different use of the LIST_Enumerate method will have a different associated Node Enumeration function that carries out a different action.

    This function has the following prototype:

            int  enumnode (node_t node, va_list ap);
    
    although the name does not need to be enumnode, but can be any name that the user desires, and will typically be different for each use of the LIST_Enumerate method call. The node parameter is a pointer to the user part of the node that is being enumerated. The ap parameter is a pointer to any additional parameters that might have been passed with the LIST_Enumerate method call. These parameters are accessed using the standard C library functions associated with the stdarg.h header file.

    If the return value is 0, then the list handling code will simply move onto the next node in the list and call the Node Enumeration function again, repeating this until the end of the list is reached. If for any reason you return a non-zero value from this function, then the enumeration of the list will be terminated immediately, and the value returned as the result of the LIST_Enumerate method call.


    Node Find Function

    This function is used when you wish to locate a particular node from within a list using the LIST_Find method call. It is called to check whether this is the node that you are trying to find.

    Very often there would only be one instance of the Node Find function for each list instance as you are probably trying to find node according to the keys you used for ordering the list. There is nothing, however, that mandates this and stops you having different versions for different LIST_Find method calls.

    This function has the following prototype:

            int  findnode (node_t node, va_list ap);
    
    although the name does not need to be findnode, but can be any name that the user desires. The node parameter is a pointer to the user part of the node that is being checked. The ap parameter is a pointer to any additional parameters that might have been passed with the LIST_Find method call. These parameters are accessed using the standard C library functions associated with the stdarg.h header file.

    The return value is interpreted as follows:

                -ve      This node is not wanted, and (for ordered lists)
                         the node is less than the desired node.
                 0       This is the node that is wanted
                 +ve     This node is not wanted, and (for ordered lists)
                         the node is greater than the desired node.
     LIST_ORDER_NA       This node is not wanted, and nothing can be said about
                         its relationship to the desired node.  This constant
                         is defined in the liblist.h header file.
    
    although in practise for unordered lists the difference between the different non-zero values is ignored (as they have to be searched serially anyway since they are unordered). For ordered lists the information is used where possible to optimise the location process by avoiding visiting every node in the list if the value returned can be used to help navigate to the desired node (assuming that it exists of course).

    Node Hash Functions

    This function is only relevant to Hash Table types of list. It is called whenever a node is inserted into a hash table, It is used to calculate the hash value that is used to determine which bucket of a hash table the new node will be inserted into. The value returned by this function will be be mod'ed with the number of nodes that are actually in the hash table, so it is not necessary for the hash function itself to do this.

    This function has the following prototype:

            int  hashnode (va_list ap);
    
    although the name does not need to be hashnode, but can be any name that the user desires. The ap parameter is a pointer to any additional parameters that might have been passed with the LIST_New method call (or one of the LIST_New??? methods that combine creating a new node with inserting it into the list.

    If the user does not supply a node hash function, then a default one will be used. This default function assumes that the definition of the user part of the node starts with a pointer to name along the lines of:

          struct {
               char * name;	/* The type is important, not the name */
               ....
               };
    

    One other point at which the Node Hash Function can be called is during the execution of a LIST_Find method call. Therefore if you are using a Hash Table class of list it is important the parameters to LIST_Find method calls also satisfy the requirements of the Node Hash Function.


    * EXAMPLE PROGRAM

    The source to this library includes the file LISTTEST.C which is used to test the library. The source is however copiously annotated, so it is worth examining it as an example of how the various properties and methods in the library can be used.

    In particular it is worth seeing how the User Supplied functions are used as this is the one area that many users initially find confusing. Once you have seen how it works, instead of being confused, you are likely to realise how it simplifies the logic of most programs to write them in this way.


    * AUTHOR and COPYRIGHT

    This library and associated documentation has been produced by:

          Dave Walker
           22 Kimptons Mead
            Potters Bar,
             Herts,
              EN6 3HZ
               United Kingdom
    
          Email:      d.j.walker@slh0101.wins.icl.co.uk
          Compuserve: 100141,2626
          MSN:        itimpi@msn.com
    
    This library and its associated documentation are copyright ©1996 by D.J.Walker. All rights are reserved.

    Every effort has been made to ensure that the LIBLIST software operates correctly as specified in this document. However no guarantee of any kind is given that the software is reliable or that the descriptions contained in this document is accurate. It is up to the user of the LIBLIST library to satisfy himself that it is suitable for the use to which it will be put.

    Permission is given to freely distribute this library, including its documentation and source as long as the following terms are adhered to:


    * RELEASE HISTORY

    May 1996: Release 1.0
    First version of the library. Released via QDOS BBS network. Does not include the RLL version of the library as the RLL system has not been made generally available for QDOS based systems.