
                        Single and Double Linked Lists

                                   "Time will run back and fetch the age
                                    of gold and speckled vanity will
                                    sicken soon and die."
                                                                 Milton

Arrays Versus Lists

          Linked lists are invaluable and should form a strategic part
     of every programmer's arsenal. If you are not familiar with linked
     lists, you have probably been using arrays to meet your list
     management needs. But arrays have serious shortcomings.

          One-dimensional arrays provide a very quick and convenient way
     of managing lists. An array is a data structure designed to store
     data about a fixed number of identical elements. For example, the
     following variable could be used to store the addresses of my two
     sisters and three brothers:

          Addresses: array [1..5] of string

          Data in the array is accessed by specifying the element
     number. For example, the following statement would update my second
     sister's address:

          Addresses[2] := '38 The Ridgeway, Friern Barnet London'

          Arrays are very easy to create, but suffer from two major
     weaknesses:

          The size of the array is fixed at compile time. In other
     words, in your source code you must explicitly state the size (or
     bounds) of the array. In our simple example, this was not a problem
     because I only have 5 siblings. Imagine, however, that you need to
     write a generic name and address program. You don't know how many
     names and addresses will be required, but you must specify the
     number in your program. The typical solution is to choose a large
     number of elements (e.g. 200) and hope that the user doesn't need
     more. Not only does this impose a limit on the program's capacity,
     it also wastes memory if the maximum number of elements is not
     reached. Borland Pascal automatically allocates the memory for the
     entire array regardless of how many elements are actually used.
     Which leads to the second shortcoming.

          An individual data item must be 64k or less, and the entire
     array is considered to be an individual item. In our example, each
     element of the array is 256 bytes, so 256 of these elements would
     consume the entire 64k. In many cases arrays simply do not have the
     capacity to meet today's data needs.

          Linked lists do not suffer from these shortcomings. With a
     linked list, each element is stored independently. The first
     element includes a pointer indicating where the second element in
     the list is stored. The second element of the list includes a
     pointer to the third element in the list, and so on. When you reach
     an element in the list that points to NIL, you know you have
     reached the end of the list. You do not need to fix the maximum
     number of elements in the list at compile time. Furthermore, the
     data is stored on the heap (not in the data segment) and so is not
     limited to 64k. The list can use all available memory.

          The good news is that you do not need to understand all the
     details of how linked lists work. Gold takes care of all the
     internal list management mechanics.

Single and Double Lists

          Gold offers singly and doubly linked lists. You need to
     understand a little list theory in order to decide which list type
     is appropriate for your application.

          A list is actually a collection of nodes. A node contains two
     primary elements: data, and one or two node pointers. The data is
     the information stored in the list, e.g. the names and addresses in
     the address book. The node pointer points to the adjacent node in
     the list, and provides a link between two nodes.

          In a single linked list, there is a single pointer at each
     node, which points to the next item in the list. If you are
     currently accessing the 100th node in the list and want to get the
     data from the 99th node, Gold would have to go all the way to the
     beginning of the list, find the pointer to the second node, go to
     the second node, find the pointer to the third node, go to the
     third node, and so on all the way to the 99th element.

          In a double linked list, there are two pointers at each node.
     One pointer points to the previous item in the list, and the other
     to the next item in the list. These are referred to as the next and
     prev pointers. In the previous example, Gold would just have to use
     the previous pointer to move to the 99th node in one step.

          Given this brief explanation of the two linked lists types,
     you might be wondering why anyone bothers with single linked lists
     since double linked lists are more powerful. The answer is two
     part: complexity and memory. Double linked lists are more
     complicated to manage than single linked lists, especially when
     inserting and deleting nodes. But, hey, you don't care because Gold
     hides all the complexity from you! Every node in a double linked
     list consumes 4 more bytes of memory (for the previous pointer)
     than the equivalent node in a single linked list. Although 4 bytes
     doesn't sound much, it can add up when you have several thousand
     nodes.

          If your lists will be short (say, 150 items or less) and you
     won't be going backwards along the list too often, use a single
     linked list. Otherwise, use a double linked list.

The Three Linked List Types

          So far we have discussed two primary linked list types: single
     and double. Gold actually provides three different types of linked
     lists: two single linked lists and one double linked list. To
     create a linked list you must declare a variable of one of the
     following types:

          List Type  Description

          StringLL   This is a single linked list designed sepcifically
                     for managing short lists of strings. These lists
                     have a low code overhead and can only be used for
                     storing strings. Behind the scenes, Gold uses this
                     list type for managing menu items, and several
                     other sets of small string lists such as the drive
                     list in the PromptDir dialog.

          SingleLL   A single linked list can be used for storing
                     strings, records or binary data, and uses a single
                     next pointer for node traversal.

          DoubleLL   A double linked list can be used for storing
                     strings, records or binary data, and uses a single
                     next pointer for node traversal. List sorting is
                     supported.

          The techniques for managing each of these list types are
     discussed in the following sections. In brief, all you do is
     declare a variable of the appropriate type (e.g. MyList: SingleLL),
     call an init procedure to initialize the variable, then call a
     series of add functions to add nodes to the list, and when you are
     finished with the list call the appropriate destroy procedure.

Using StringLLs

          The StringLL is really the baby of the Gold's linked list
     family. It can only store strings and shouldn't be used to store
     more than forty (or so) elements, otherwise performance will
     suffer. Having said that, there are literally hundreds of
     situations where all you need is a list to manage  several dozen
     strings. It might be simple, but it's very useful.

Initializing the StringLL

          Before you can add items to (or populate) a StringLL it must,
     must, must be initialized using the procedure StrLLInit. For
     example:

          StrLLInit(MyList);

          All the names of procedures used to manipulate a StringLL list
     are prefixed with the characters "STRLL".

Building the List

          Having initialized the list, you can add strings to the list
     using the following procedure:

          StrLLAdd(var SL:StringLL; Str:String): integer;

          Adds a new string node at the end of the passed StringLL. The
     function returns an integer indicating the success of the
     operation: a 0 indicates the item was successfully added and a 1
     indicates failure (probably due to a lack of memory).

          There is no way to change data in StringLL, nor can you delete
     a node or insert a node. As McDonalds might say: "What you add is
     what you get"! If you need more list manipulation tools, switch to
     a SingleLL or DoubleLL.

Getting String Data out of the List

          The following function is used to get a string from a linked
     list:

          StrLLGetStr(var SL:StringLL;Num:integer): string;

          The function is passed a StringLL and a (one-based) node
     number and returns the string stored at that node.

Destroying the StringLL

          After you have finished with the list, always destroy the
     list, and in so doing free up the memory used to create the list
     with the following procedure:

          StrLLDone(var SL:StringLL);

     Frees the memory used by all the nodes in a StringLL.

A StringLL Example

          Listed below is an extract of the DEMLNK1.PAS demo file which
     creates a StringLL and then displays the list contents using the
     PromptOKStrLL procedure.

          var
             I: integer;
             TextList: StringLL;

          begin
             I := 0;
             StrLLInit(TextList);
             inc(I,StrLLAdd(TextList,'Thank ...'));
             inc(I,StrLLAdd(TextList,''));
             inc(I,StrLLAdd(TextList,'This ...'));
             inc(I,StrLLAdd(TextList,'of ...'));
             inc(I,StrLLAdd(TextList,'product to others.'));
             inc(I,StrLLAdd(TextList,''));
             inc(I,StrLLAdd(TextList,'A ...'));
             inc(I,StrLLAdd(TextList,'in ...'));
             inc(I,StrLLAdd(TextList,'of the ....'));
             if I = 0 then {all's well}
                PromptOKStrLL(' Welcome ',TextList)
             else
                PromptOK(' Ouch! ',' Not enough memory');
             StrLLDestroy(TextList);
          end.


          Notice the clever trick to avoid testing the return codes from
     the StrLLAdd function. "I" is incremented by the return code of
     each StrLLAdd call. If "I" is zero after all the calls, then every
     add operation must have been successful. The code make look a
     little strange, but it is a useful technique.Understanding Node
     Pointers

          To get the most from Gold's SingleLL and DoubleLL linked lists
     you need to have a modest understanding of node pointers.

          As you know, a linked list is a collection of nodes, with each
     node pointing to the next node in the list. To access the data
     stored at a node, you need to specify which node you want to
     access. This is done using a node pointer.

          For example, to change a string stored in a SingleLL you would
     use the function SLLChangeStr, and pass a pointer to the node along
     with the replacement string. If you wanted to change the string
     stored at the 20th node, for example, you will need to obtain a
     pointer to the node. Fortunately Gold includes the function
     SLLNodePtr expressly for this purpose. You pass a node number and
     the function returns a pointer to the node.

          The following code fragment shows how this might be
     implemented in a program:

          var
             SNP: SingleNodePtr;
             Result: integer;
          begin
             ...
             SNP := SLLNodePtr(20);
             Result := SLLChangeStr(SNP,'New string');
             ...
          end.

          Alternatively, you could avoid the need for a variable and
     combine the two calls into one expression as follows:

          var
             Result: integer;
          begin
             ...
             Result := SLLChangeStr(SLLNodePtr(20),'New string');
             ...
          end.

          Anywhere that a SingleNodePtr is required as a parameter, you
     can substitute the function SLLNodePtr.

          The examples (above) refer to single linked lists, but the
     same goes for double linked lists -- use the function DLLNodePtr to
     a return node pointer of type DoubleNodePtr.

Using SingleLLs

          The SingleLL type is used to create a single linked list. Gold
     includes a generic set of routines for managing any data in a
     SingleLL. However, because many applications used linked lists to
     store strings, there is also a set of routines designed especially
     for single linked lists of strings.

Initializing a SingleLL Variable

          A SingleLL variable must be initialized before data can be
     added to the list. There are two procedures for initializing
     SingleLLs: one for generic lists and one for string lists, as
     follows:

          InitSLL(var TheList:SingleLL);

          Initializes a single linked list and sets the list for storing
     any data.

          InitSLLStr(var TheList:SingleLL);

          Initializes a single linked list and sets the list for storing
     strings.

          The type SingleLL is declared in GOLDLINK as follows:

          SingleLL = record
             StartNodePtr: SingleNodePtr;
             EndNodePtr: SingleNodePtr;
             TotalNodes: longint;
             StrVars: boolean;
             Dirty: boolean;
          end; {SingleLL}

Giving a SingleLL Focus

          Gold allows an application to have many linked lists active at
     the same time, i.e. two or more lists in the same scope. Gold needs
     to know which list to manipulate. Before calling the functions to
     add, modify and delete data in a list, you must instruct Gold to
     give a single linked list focus using the following procedure:

          SLLSetActiveList(var S:SingleLL);

          Instructs Gold to direct all single linked list management
     functions to the specified list.

          If you are managing multiple lists, you can use the procedure
     SLLActivatePrevList to change the focus back to the list which had
     focus prior to the last SLLSetActiveList call.

Adding and Changing List Data

          Once a list has been given focus, you can use the following
     procedures and functions to add and modify the list data:

          SLLAdd(var TheData; Size:longint): integer;

          Adds data of any type to a new node at the end of a single
     linked list. The function is passed a variable, followed by a
     longint indicating the size of the variable. The function returns a
     0 if successful.

          SLLInsertBefore(Node:SingleNodePtr;var TheData;Size:longint):
          integer;

          Inserts a new node in front of the node specified by the node
     pointer. The function returns a 0 if successful.

          SLLChange(Node:SingleNodePtr;var TheData;Size:longint):
          integer;

          Modifies the data stored at the specified node. The procedure
     is passed a pointer to the node where the new data will be stored,
     the data variable and the data size. The function returns a 0 if
     successful.

          SLLDelNode(Node:SingleNodePtr);

          Removes the specified node from the linked list.

          SLLGetNodeData(Node:SingleNodePtr;Var TheData);

          Retrieves the data at the specified node, and copies the
     contents into the specified variable.

          SLLGetNodeDataSize(Node:SingleNodePtr):longint;

          Returns the number of bytes of data stored at the specified
     node.

Storing Records in a List

          Many of the SLL functions accept an untyped variable as one of
     the parameters. This allows the list to manage data of any type,
     e.g. integers, reals, records, enumerated types, etc. But
     ninety-nine percent of applications store either records or strings
     in linked lists.

          If you want to save a record in a node, just create a variable
     of the record's type, and then use the SLL add function to copy the
     record to a new node at the end of the list. For example, the
     following code stores a name and address at a node:

          type
             Location: record
                Name: string[20];
                Address: string[50];
                Zip: longint;
             end; {Location}

          var
            WorkAddress: Location
             AddressList: SingleLL;

          begin
             ...
             InitSLL(AddressList);
             SLLSetActiveList(AddressList);
             with WorkAddress do
             begin
                Name := 'Beavis';
                Address := '1 Bonehead Park, Dallas TX';
                Zip := 77665;
             end;
             SLLAdd(WorkAddress,sizeof(Address));
             ...
          end.

          That's all there is to it. In a real application you might add
     hundreds of addresses to the list. Retrieving a record from the
     list is just as easy. The following statement will retrieve the
     twelfth address from the list:

          SLLGetNodeData(SLLNodePtr(12),WorkAddress);

Storing Strings in a List

          If you have initialized the list using InitSLLStr (to store
     strings) then you should use the following functions to add, modify
     and access strings in the list:

          SLLAddStr(Str:string):integer;

          Adds a new node to the end of a single linked list and stores
     the string at the node. The function returns a 0 if successful.

          SLLInsStrBefore(Node:SingleNodePtr;Str:string): integer;

          Inserts a new node, prior to the node specified by the node
     pointer, and stores a string at the node. The function returns a 0
     if successful.

          SLLChangeStr(Node:SingleNodePtr;Str:string): integer;

          Modifies the string stored at the specified node. The
     procedure is passed a pointer to the node where the new string will
     be stored, along with the string. The function returns a 0 if
     successful.

          SLLGetStr(Num:longint):string;

          Returns the string at the specified node number. Note (for
     convenience) that this function accepts a node number and not a
     node pointer.

Populating a List from a File

          Gold makes it easy to save and restore lists from a text file
     using the following two procedures:

          SLLLoadFromFile(Filename:string):integer;

          Populates the active linked list from a text file. Each line
     of the text file is a node in the linked list. Any existing data in
     the active linked list is destroyed. The active linked list must be
     initialized as a string linked list. The function returns a zero if
     the read was successful.

          SLLSaveToFile(Filename:string):integer;

          Saves the contents of the active single linked list to a text
     file. Each node of the list is saved as a line of text in the file.
     The function returns a zero if the save was successful. An existing
     file of the same name will be overwritten.

Settings and Getting Bits

          Every node in a SingleLL (and a DoubleLL for that matter) has
     a special byte used to store up to eight bit flags. These bit flags
     are numbered from 0 through 7 and bits 0 and 1 are reserved for
     Gold's use -- for item tagging and color separation in list windows
     (discussed in the next chapter). The remaining 6 bits, numbered 2
     through 7, are available for use. The following two routines are
     used to manage these bits.

          SLLSetBit(Node:SingleNodePtr; BitPos:byte; On:boolean);

          Sets the bit at the specified node to TRUE or FALSE.

          SLLGetBit(Node:SingleNodePtr; BitPos:byte): boolean;

          Returns TRUE if the specified bit at the node is on, or FALSE
     if it is off.

Destroying the List

          The nodes in a linked list are constructed on the heap and can
     consume significant amounts of memory. When the list is no longer
     required, always destroy the list and free the associated memory by
     calling the following procedure:

          SLLDestroy;

          Disposes of all the nodes in the active SingleLL and frees all
     the memory used by the list.

Using DoubleLLs

          The DoubleLL type is used to create a doubly linked list. The
     Gold routines to manage double linked lists are similar to the ones
     for managing single linked lists, except that the procedure and
     function names begin with DLL instead of SLL.

          Listed below are the DoubleLL procedures and functions which
     behave in the same manner as their corresponding SLL functions
     discussed in the previous section:

Initializing a DoubleLL variable

     InitDLL(var TheList:DoubleLL);
     InitDLLStr(var TheList:DoubleLL);

Giving a DoubleLL Focus

     DLLSetActiveList(var D:DoubleLL);
     DLLActivatePrevList;

Node Pointer Management

     DLLNodePtr(NodeNumber:longint): DoubleNodePtr;

Adding and Changing List Data

     DLLAdd(var TheData;Size:longint): integer;
     DLLChange(Node:DoubleNodePtr;var TheData;Size:longint): integer;
     DLLInsertBefore(Node:DoubleNodePtr;var TheData;Size:longint): int
     DLLDelNode(Node:DoubleNodePtr);
     DLLGetNodeData(Node:DoubleNodePtr;Var TheData);
     DLLGetNodeDataSize(Node:DoubleNodePtr):longint;

Storing Strings in a List

     DLLAddStr(Str:string):integer;
     DLLGetNodeStr(Node:DoubleNodePtr;Start,Finish: longint): string;
     DLLGetStr(Num:longint): string;

Populating a List from a File

     DLLLoadFromFile(Filename:string):integer;
     DLLSaveToFile(Filename:string):integer;

Settings and Getting Bits

     DLLSetBit(Node:DoubleNodePtr; BitPos:byte; On:boolean);
     DLLGetBit(Node:DoubleNodePtr; BitPos:byte): boolean;
     Destroying the List
     DLLDestroy;

Defining a Custom String Function

          A DoubleLL can store any form of binary data. By default, Gold
     will convert this data to string format whenever the functions
     DLLGetStr or DLLGetNodeStr are called. When the data is binary, all
     Gold can do is assume the data represents characters, and move the
     data into a string format.

          Since you know the format of the data (such as a name and
     address record), and you can probably return a much more elegant
     string than this default function. Gold allows you to write your
     own "binary to string" function which will be used in place of
     Gold's default function.

          All you have to do is create a function following some
     specific rules and then call the procedure DLLAssignGetStrFunc to
     instruct Gold to call your function every time a binary to string
     conversion is required.

          For a procedure to be eligible as a get string function it
     must adhere to the following rules:

          The procedure must be declared as a far procedure at the root
          level. Refer to the section Understanding Hooks in chapter 3
          for further information.

          The procedure must be declared with three passed parameters.
          The first parameter must be of type DoubleNodePtr, the second
          and third parameters of type longint -- these numbers
          represent the starting and ending characters to be returned.

     The following procedure declaration follows these rules:

          {$F+}
          function AddrStr(Node:DoubleNodePtr;Start,Finish: longint):
          string;
          {}
          begin
             AddStr := ....
          end; { AddrStr}
          {$F-}

          The following procedure is then called to instruct Gold to
     call your procedure to test whether two nodes are in the wrong
     order:

     DLLAssignStrFunc(Func:DLLGetStrFunc);

          Instructs Gold to call the specified function when getting
     node data in string format.

Additional Node Management Functions

          Behind the scenes, a double linked list has a pointer called
     the ActiveNodePtr. This pointer is similar to a file cursor, in
     that it points to a node (or line, if you will) in the list.
     Whenever you need to access a node in the list, the ActiveNodePtr
     is set to point to that node. This can speed list management
     operations significantly because there is a high probability that
     the next node to be accessed will be adjacent to the active node
     pointer -- rather than traversing from one end of the list to get
     to the new node, Gold simply shifts one node from the
     ActiveNodePtr.

          In most applications you won't need to worry about the
     ActiveNodePtr. However, in addition to the standard node pointer
     function DLLNodePtr, you can use the following procedures to
     manipulate the ActiveNodePtr:

          DLLAdvance(Amount:longint);

     Moves the ActiveNodePtr Amount nodes down the list.

          DLLRetreat(Amount:longint);

     Moves the ActiveNodePtr Amount nodes up the list.

          DLLJump(NodeNumber:longint);

     Moves the ActiveNodePtr to the specified node number.

          DLLShiftActiveNode(NewNode: DoubleNodePtr; NodeNumber:
          longint);

          Sets the ActiveNodePtr and the ActiveNodeNumber to the
     specified values.

          DLLSwapNodes(Node1,Node2:DoubleNodePtr);

     Swaps the data stored at the two nodes.

Additional Bit Management Functions

          In addition to the standard DLLSetBit and DLLGetBit functions,
     there are the following bit management functions:

          DLLGetTagState(Num:longint):boolean;

          Returns the value of the tag bit (which defaults to bit 0).
     This bit is set by the user in a list window when altering the tag
     state of the item.

          DLLDelAllStatus(BitPos:byte;On:boolean);

          Deletes all nodes in the list which have the specified bit set
     to the specified state.


          The demo file DEMLNK2.PAS illustrates many of the DoubleLL
     functions.

List Sorting

          One fundamental advantage of DoubleLL lists is that the nodes
     can be sorted. Gold has a default sort function which tests the
     data stored at the node like a string, regardless of whether the
     data is actually a string. To sort the list, simply call the
     following procedure:

          DLLSort(SortID:shortint; Ascending:boolean);

          Sorts the list (i.e. reorders all the nodes) in ascending or
     descending order.  The SortID is only relevant to custom sort
     procedures, and should be specified as zero for the default sort.

          If you are using the DoubleLL to store non-string data, you
     might want to incorporate your own sorting routines. For example,
     you might store names and address as records, and want to sort on
     the zip field. If you tend to panic at the thought of having to
     write a sort algorithm, fear not! At the heart of every sort
     algorithm is a simple test to compare whether two nodes are in the
     correct order. For example, is "Bean" before "Ainsbury"? If the
     response is FALSE, the sort function will swap the two nodes, and
     process with the next comparison.

          Gold allows you to use a custom function (or sort hook) to
     perform your own wrong order function. Whenever Gold needs to
     decide whether two nodes are in the wrong order, your custom
     function will be called. All you have to do is create a function
     following some specific rules and then call the  procedure
     DLLAssignWrongOrderFunc to instruct Gold to call your function
     every time a node comparison is required.

          For a procedure to be eligible as a wrong order function it
     must adhere to the following rules:

          The procedure must be declared as a far procedure at the root
          level. Refer to the section Understanding Hooks in chapter 3
          for further information.

          The procedure must be declared with four passed parameters.
          The first parameter must be of type shortint, the second and
          third parameters of type DoubleNodePtr variable, and the
          fourth parameter of type boolean.

     The following procedure declaration follows these rules:

          {$F+}
          function SortHook(SortID:shortint;
          Node1,Node2:DoubleNodePtr; Asc:boolean): boolean;
          {}
          begin
             ...
          end; { SortHook}
          {$F-}

          The following procedure is then called to instruct Gold to
     call your procedure to test whether two nodes are in the wrong
     order:

          DLLAssignWrongOrderFunc(Func:DLLWrongOrderFunc);

          Instructs Gold to call the specified function when sorting a
     double linked list.

          The first parameter passed to the wrong order function is the
     parameter which you used when calling the DLLSort function, and it
     is designed to allow you to sort a list in more than one way. The
     example below uses the SortID to decide which field of a record to
     sort on. The second and third parameters are pointers to the two
     nodes which are being sorted. The final parameter is a boolean
     indicating which order to sort the list: ascending or descending.

          The best way to explain the sorting concepts is by example.
     Let's assume the active double linked list has been used to store
     records of the following type:

        Location: record
           Name: string[20];
           Address: string[50];
           Zip: longint;
        end; {Location}


          The following function could be used to allow sorting on any
     one of the three fields based on the SortID.

          {$F+}
          function RecordSort(SortID:shortint;
          Node1,Node2:DoubleNodePtr; Asc:boolean): boolean;
          {}
          var P1,P2:pointer;
          begin
             P1 := Node1;
             P2 := Node2;
             case SortID of
                1: begin
                   if Asc then
                      RecordSort := Location(P1^).Name
                                    > Location(P2^).Name
                   else
                      RecordSort := Location(P2^).Name
                                    > Location(P1^).Name;
                end;
                2: begin
                   if Asc then
                      RecordSort := Location(P1^).Address
                                    > Location(P2^).Address
                   else
                      RecordSort := Location(P2^).Address
                                    > Location(P1^).Address;
                end;
                3: begin
                   if Asc then
                      RecordSort := Location(P1^).Zip
                                    > Location(P2^).Zip
                   else
                      RecordSort := Location(P2^).Zip
                                    > Location(P1^).Zip;
                end;
             end;
          end; { RecordSort}
          {$F-}

          begin
             ...
             DLLAssignWrongOrderFunc(RecordSort);
             ...
          end.

          To sort the example list by name in ascending order you would
     make the following call:

          DLLSort(1,true);

          To sort the example list by zip in descending order you would
     make the following call:

          DLLSort(3,false);

