An alternate LinkedList

Share your advanced PureBasic knowledge/code with the community.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

An alternate LinkedList

Post by Xombie »

Code updated For 5.20+

Howdy. For the project I'm working on, I really needed a way to get a dynamically sized array in my structures. Can't happen with PB. Okay. And I can't use the LinkedList functions because I need the array inside of my structure, and sometimes the dynamic array would be a structure type with another dynamic array (of a different type). Possible in VB, not PB.

So, I threw together a home brew linked list 'class'. The main difference (aside from ugly, unoptimized and bug-ridden code) is that you create this linked list and obtain an address to the whole linked list. So you can reference the whole linked list via a long variable. Which is easily stored in a structure. So you could have...

Code: Select all

;- Notes
; 1.  Took some notes from PolyVector on how to make improvements and how using the address of procedures works.
;     I was always wondering why you would want the address of a procedure and what you'd do with it ^_^
; 2.  Sorting example uses QuickSort.pb code from the "Sources - Advanced" examples.
;- Updates
; 1/27/2005
;     1.  Added xlPosition() to return the index of the current element.  Maybe I'll think
;         about adding a Position variable to each element to speed this part up?  More
;         memory usage that way, however.
;     2.  Fixed up a few more minor bugs.
; 1/24/2005
;     1.  Put in some minor bug checking to make sure a non-zero *xList value is passed
;         to our public functions.  Still some to add and other things to check.
;     2.  Changed AddAt() to Insert().  Actually might have been in a previous example.
; 1/7/2005
;     1.  Some bug fixes.  For example, deleting the last item wasn't setting the Last item address.
;     2.  Additional examples - how to have a linked list of just long values.
;     3.  Updated the QuickSort routines.  Moved the Current/Bookmark swapping routines
;         to xl_Swap() as an 'optional' parameter.  Also, listing an example of how you
;         would sort a linked list of long values.  You'll want to update any of your 
;         older Quicksort routines to work with this.
; 12/27/2004
;     1. Updated to include xlUpdate() function to update the value for an existing
;        element.
;     2. Cleaned xlAdd() and xlAddAt() functions to remove variables not used.
;     3. Fixed how the Current field is used when sorting.
;     4. Fixed xlValue() to actually do what it's supposed to do.
;     5. Added xlBookmark() to remember a position and xlRecall() to go back to that bookmark.
;- Constants
#xlNull = -1
#xlEmpty = 0
;- Structures
Structure s_Test
  Named.s
  lVar.l
  bVar.b
  sVar.s
EndStructure
Structure s_xListInfo
  Count.l
  ; A count of the nodes in the list.
  CurrentIndex.l
  ; Holds the index of the current element.
  Current .l
  ; Points to the address for our last selected node.
  Last.l
  ; A pointer to the last node.
  Bookmark.l
  ; A bookmark pointer.  This is used if we want to remember our 'Current' place
  ; while looping through the linked list.  Possibly make into a static array?
  ; Like 10 bookmarks and pass index 1-10 to retrieve/set whatever bookmark?
  StructureSize.l
  ; The size of our value structure.
  AddChangesCurrent.b
  ; Controls the behavior of the Current pointer when adding a node.  If True, will set
  ; the Current pointer to our new node.  If False, will not change the Current pointer.
  AddCausesSort.b
  ; Controls whether our sorting routine is called when adding a new item.  If True, our
  ; sort function will be called every time a new element is added.  Probably not the 
  ; best way to do this.  Maybe two separate functions?  One for just sorting and one
  ; for sorting when adding a new element?  If your list is already sorted and this is
  ; called would it be faster to resort the whole thing or make a smart sorting procedure?
  ;
  ; Not implemented yet.
  ;
  AddressCopy.l
  ; The address of our CopyStructure Procedure.  When using structures with xList there 
  ; needs to be two custom procedures - a procedure to copy the structure and a procedure
  ; to 'clear' the structure.
  AddressEmpty.l
  ; This will hold the address of our clearing routine.  Use this to set defaults or just
  ; to set an empty structure.  When we look for an item based on it's index and don't find
  ; it, we return an emptied structure - using this procedure.  So if you want to return
  ; a certain set of values, set them in this procedure.
  AddressSort.l
  ; This will hold the address of our custom sorting routine.  Tailored to the structure.
EndStructure
Structure s_xList
  ; Our linked list structure.
  Left.l
  ; Points to our previous node (or the information structure if at the root).
  Right.l
  ; Points to the next node (or an #xlEmpty constant if there is no next node).
  Value.l
  ; Holds the pointer to our structure
EndStructure
;- Private Procedures
;/ This block of procedures are the 'private' functions that shouldn't be called by the user.
Procedure xl_Value(*xlNode.s_xList) ; A simple 'Private' function that returns the address of our value structure based on a node's address.
  If *xlNode <> #xlNull : ProcedureReturn *xlNode\Value : Else : ProcedureReturn 0 : EndIf
EndProcedure
Procedure.s xl_SetValue(*xlNode.s_xList, inValueAddress) ; A simple 'Private' function that set's a node's value based on the node's address.
  If *xlNode <> #xlNull : *xlNode\Value = inValueAddress : EndIf
EndProcedure
Procedure xl_SetLAddress(*xlNode.s_xList, Address) ; A 'Private' function to set the left pointer address of a node.
  If *xlNode > 0 : *xlNode\Left = Address : EndIf
  ; Make sure we aren't trying to set an #xlEmpty (non-node) item's address.
EndProcedure
Procedure xl_SetRAddress(*xlNode.s_xList, Address) ; A 'Private' function to set the right pointer address of a node.
  If *xlNode > 0 : *xlNode\Right = Address : EndIf
  ; Make sure we aren't trying to set an #xlEmpty (non-node) item's address.
EndProcedure
Procedure xl_NodeLeft(*xlNode.s_xList) ; A 'Private' function to return the address of the Node left of *xlNode
  If *xlNode > 0 : ProcedureReturn *xlNode\Left : EndIf
EndProcedure
Procedure xl_NodeRight(*xlNode.s_xList) ; A 'Private' function to return the address of the Node right of *xlNode
  If *xlNode > 0 : ProcedureReturn *xlNode\Right : EndIf
EndProcedure
Procedure xl_InsertItem(*xlNode.s_xList, inValue, bInserting.b) ; A 'Private' function to insert a new node.  Not to be called by the user.
  ;
  Protected *xlNewNode.s_xList
  ;
  *xlNewNode = AllocateMemory(SizeOf(s_xList))
  ; Create space for our new item in memory.
  *xlNewNode\Left = *xlNode
  ; Make sure the left points to the item before this one.
  If bInserting = #False : *xlNewNode\Right = #xlEmpty : Else : *xlNewNode\Right = *xlNode\Right : EndIf
  ; If we are inserting a value in the middle of our list then we need to make sure our right pointer
  ; points to the next item in the list.  Otherwise, we're adding at the end so the next item is an
  ; empty pointer.
  If bInserting = #True : xl_SetLAddress(*xlNewNode\Right, *xlNewNode) : EndIf
  ; Since we're inserting a new node, we're replacing the old node with the information from the added node.
  ; We're then calling xl_InsertItem to add the *old* item back as a right node to the added node.  Since the
  ; old node (which is being created here) is using a new address we have to update it's next item's left 
  ; address pointer to this 'old' node.
  *xlNewNode\Value = inValue
  ; We're passing the address of our value structure so just store it here.
  ProcedureReturn *xlNewNode
  ;
EndProcedure
Procedure xl_IndexFromAddress(*xList.s_xList, inAddress)
  Protected iLoop
  If *xList
    Repeat
      If *xList = inAddress : ProcedureReturn iLoop : EndIf
      *xList = *xList\Right
      iLoop + 1
    Until *xList\Right = #xlEmpty
  EndIf
  ProcedureReturn -1
EndProcedure
Procedure xl_Swap(*Element01.s_xList, *Element02.s_xList, *xList.s_xList) ; Swap the two nodes based on addresses.  Pass *xlInfo to swap current & bookmark.
  Protected lHold
  ; A variable to hold one of our values.
  Protected *xlInfo.s_xListInfo
  ;
  If *Element01 = *Element02 : ProcedureReturn : EndIf
  ; Can't swap the same indexes.
  *xlInfo = *xList\Left 
  ; 
  lHold = *Element01\Value
  *Element01\Value = *Element02\Value
  *Element02\Value = lHold
  ;
  If *xList
    ; If non-zero we want to check the Current and Bookmark fields to see
    ; if they need to be swapped as well.  Although we move the addresses
    ; of the values and not the addresses of the elements themselves, we
    ; are setting Current and Bookmark based on the index of the values.
    If *xlInfo\Current = *Element01
      *xlInfo\Current = *Element02
    ElseIf *xlInfo\Current = *Element02
      *xlInfo\Current = *Element01
    EndIf
    ;
    If *xlInfo\Bookmark = *Element01
      *xlInfo\Bookmark = *Element02
    ElseIf *xlInfo\Bookmark = *Element02
      *xlInfo\Bookmark = *Element01
    EndIf
    ;
    If *xlInfo\CurrentIndex = xl_IndexFromAddress(*xList, *Element01)
      *xlInfo\CurrentIndex = xl_IndexFromAddress(*xList, *Element02)
    ElseIf *xlInfo\CurrentIndex = xl_IndexFromAddress(*xList, *Element02)
      *xlInfo\CurrentIndex = xl_IndexFromAddress(*xList, *Element01)
    EndIf
    ; Ouch.  That last block is going to be slow :(
  EndIf
  ;
EndProcedure
;- Public Procedures - Order Importance
;/ This is needed for the sorting routine and other functions so I'm putting it at the top.
Procedure xlAddress(*xList.s_xList, index) ; Return the address of a node based on it's Index
  Protected NodeCount
  If *xList
    If *xList\Right <> #xlNull
      While *xList\Right <> #xlEmpty And NodeCount <> index
        *xList = *xList\Right
        NodeCount + 1
      Wend
      If NodeCount = index
        ProcedureReturn *xList
        ; Return our address.
      EndIf
    Else
      ProcedureReturn -1
      ; Empty list, return -1
    EndIf
    ;
  EndIf
  ;
  ProcedureReturn -1
  ; We didn't find our node.
EndProcedure
;- Custom Procedures
;/ These four procedures are needed for each structure.  So for every different list we create, we have to create
;/ new functions using these as templates.  For demonstration purposes I list two sort routines but only one is 
;/ 'needed'.  It's okay to change the procedure names and procedure variable names but you must keep the same
;/ number of variables in the procedure line.  For example, you can call the copy procedures variables 
;/ "*sCopyTo.whatever_your_structure_is" & "*sCopyFrom.whatever_your_structure_is"
;/ instead of
;/ "*strctCopyTo.s_Test" & "*strctCopyFrom.s_Test"
;/ but the first variable *must* be the structure you are copying to and the second must be the structure you are
;/ copying from and they both must be pointers.  So, basically, changing the names are okay but not the number or
;/ type.
Procedure xl_LongCopyStructure(*strctCopyTo, *strctCopyFrom)
  PokeL(*strctCopyTo, PeekL(*strctCopyFrom))
  ; Since we're dealing with the address of the long variable and not the
  ; long variable itself, we need to 'peek' at the "Copy From" variable to
  ; get the value and then poke that value into the "Copy To" variable.
EndProcedure
Procedure xl_LongEmptyStructure(*StructureToEmpty)
  PokeL(*StructureToEmpty, 0)
  ; Poke a 0 (zero) value into the value 'structure' (a long var, really)
EndProcedure
Procedure xl_LongQuickSort(*xList.s_xList, g, d) ; See description in procedure.
  ; This is a customized quicksort routine to sort by the LVar field in our
  ; 'Test' structure.  Mainly to show you how sorting by structures will work.
  ; You can use this as a template and just change the structure and field names
  ; to match what you are doing.  Don't forget to change v's variable type to
  ; the same type of variable you are testing against.  See xl_TestLVarQuickSort
  ; to see how sorting based on a string field in the same structure works.
  ;
  Protected *xlHold
  Protected *Element.s_xList
  Protected *Element02.s_xList
  Protected *xlHold02
  Protected *xlInfo.s_xListInfo
  ;
  *xlInfo = *xList\Left
  ;
  If g < d
    *Element = xlAddress(*xList, d)
    *xlHold = xl_Value(*Element)
    v = PeekL(*xlHold)
    i = g-1
    j = d 
    Repeat
      Repeat
        i=i+1
        *Element = xlAddress(*xList, i)
        *xlHold = xl_Value(*Element)
      Until PeekL(*xlHold) >= v 
      ok = 0
      Repeat
        If j>0 : j=j-1 : Else : ok=1 : EndIf 
        *Element = xlAddress(*xList, j)
        *xlHold = xl_Value(*Element)
        If PeekL(*xlHold) <= v : ok=1 : EndIf
      Until ok<>0 
      *Element = xlAddress(*xList, i)
      *Element02 = xlAddress(*xList, j)
      xl_Swap(*Element, *Element02, *xList)
      ;
    Until j <= i
    ;
    xl_Swap(*Element, *Element02, *xList)
    ;
    *Element02 = xlAddress(*xList, d)
    xl_Swap(*Element, *Element02, *xList)
    ;
    xl_LongQuickSort(*xList, g, i-1)
    xl_LongQuickSort(*xList, i+1, d)
    ;
  EndIf
  ;
EndProcedure
Procedure xl_LongSort(*xList.s_xList)
  Protected *xlInfo.s_xListInfo
  *xlInfo = *xList\Left
  xl_LongQuickSort(*xList, 0, *xlInfo\Count-1) 
EndProcedure
;/
Procedure xl_TestCopyStructure(*strctCopyTo.s_Test, *strctCopyFrom.s_Test)
  *strctCopyTo\Named = *strctCopyFrom\Named
  *strctCopyTo\lVar = *strctCopyFrom\lVar
  *strctCopyTo\bVar = *strctCopyFrom\bVar
  *strctCopyTo\sVar = *strctCopyFrom\sVar
EndProcedure
Procedure xl_TestEmptyStructure(*StructureToEmpty.s_Test)
  *StructureToEmpty\Named = ""
  *StructureToEmpty\lVar = -1 ; In our test, this will help identify bad return values.
  *StructureToEmpty\bVar = 0
  *StructureToEmpty\sVar = "Empty"
EndProcedure
Procedure xl_TestLVarQuickSort(*xList.s_xList, g, d) ; See description in procedure.
  ; This is a customized quicksort routine to sort by the LVar field in our
  ; 'Test' structure.  Mainly to show you how sorting by structures will work.
  ; You can use this as a template and just change the structure and field names
  ; to match what you are doing.  Don't forget to change v's variable type to
  ; the same type of variable you are testing against.  See xl_TestLVarQuickSort
  ; to see how sorting based on a string field in the same structure works.
  ;
  Protected *xlHold.s_Test
  Protected *Element.s_xList
  Protected *xlHold02.s_Test
  Protected *xlInfo.s_xListInfo
  ;
  *xlInfo = *xList\Left
  ;
  If g < d
    *Element = xlAddress(*xList, d)
    *xlHold = xl_Value(*Element)
    v = *xlHold\lVar
    i = g-1
    j = d 
    Repeat
      Repeat
        i=i+1
        *Element = xlAddress(*xList, i)
        *xlHold = xl_Value(*Element)
      Until *xlHold\lVar >= v 
      ok = 0
      Repeat
        If j>0 : j=j-1 : Else : ok=1 : EndIf 
        *Element = xlAddress(*xList, j)
        *xlHold = xl_Value(*Element)
        If *xlHold\lVar <= v : ok=1 : EndIf
      Until ok<>0 
      *xlHold = xlAddress(*xList, i)
      *xlHold02 = xlAddress(*xList, j)
      xl_Swap(*xlHold, *xlHold02, *xList)
      ;
    Until j <= i
    ;
    xl_Swap(*xlHold, *xlHold02, *xList)
    ;
    *xlHold02 = xlAddress(*xList, d)
    xl_Swap(*xlHold, *xlHold02, *xList)
    ;
    xl_TestLVarQuickSort(*xList, g, i-1)
    xl_TestLVarQuickSort(*xList, i+1, d)
    ;
  EndIf
  ;
EndProcedure
Procedure xl_TestNamedQuickSort(*xList.s_xList, g, d) ; See description in procedure 
  ;
  Protected *xlHold.s_Test
  Protected *Element.s_xList
  Protected *xlHold02.s_Test
  Protected *xlInfo.s_xListInfo
  ;
  *xlInfo = *xList\Left
  ; 
  If g < d
    *Element = xlAddress(*xList, d)
    *xlHold = xl_Value(*Element)
    v.s = *xlHold\Named
    i = g-1
    j = d 
    Repeat
      Repeat
        i=i+1
        *Element = xlAddress(*xList, i)
        *xlHold = xl_Value(*Element)
      Until *xlHold\Named >= v 
      ok = 0
      Repeat
        If j>0 : j=j-1 : Else : ok=1 : EndIf 
        *Element = xlAddress(*xList, j)
        *xlHold = xl_Value(*Element)
        If *xlHold\Named <= v : ok=1 : EndIf
      Until ok<>0 
      *xlHold = xlAddress(*xList, i)
      *xlHold02 = xlAddress(*xList, j)
      xl_Swap(*xlHold, *xlHold02, *xList)
    Until j <= i
    ;
    xl_Swap(*xlHold, *xlHold02, *xList)
    *xlHold02 = xlAddress(*xList, d)
    xl_Swap(*xlHold, *xlHold02, *xList)
    ;
    xl_TestNamedQuickSort(*xList, g, i-1)
    xl_TestNamedQuickSort(*xList, i+1, d)
    ;
  EndIf
  ;
EndProcedure
Procedure xl_TestSortBylVar(*xList.s_xList)
  ; Note that we can create as many sort routines as we want, to sort on different values.  Just be sure to call
  ; xlSetSortProcedure with the sorting procedure you wish to use before sorting.  So if you want to sort by lVar
  ;
  ; xlSetSortProcedure(q, @xl_TestSortBylVar())
  ; xlSort(q)
  ;
  ; Or, if you want to sort by Named...
  ;
  ; xlSetSortProcedure(q, @xl_TestSortBylNamed())
  ; xlSort(q)
  ;
  Protected *xlInfo.s_xListInfo
  *xlInfo = *xList\Left
  xl_TestLVarQuickSort(*xList, 0, *xlInfo\Count-1) 
  ; Hopefully you get the idea.  Just put whatever sorting routines you prefer here.
EndProcedure
Procedure xl_TestSortByNamed(*xList.s_xList)
  ; Set xlTestSortBylVar for description.
  Protected *xlInfo.s_xListInfo
  *xlInfo = *xList\Left
  xl_TestNamedQuickSort(*xList, 0, *xlInfo\Count-1)
EndProcedure
;- Public Procedures
Procedure xlSetCopyProcedure(*xList.s_xList, AddressOfCopyProcedure)
  ; Set the pointer to the copy structure procedure.  Shouldn't need to call this since 
  ; we set it from xlCreate.
  Protected *xlInfo.s_xListInfo
  If *xList = 0 : ProcedureReturn : EndIf
  *xlInfo = *xList\Left
  *xlInfo\AddressCopy = AddressOfCopyProcedure
EndProcedure
Procedure xlSetEmptyProcedure(*xList.s_xList, AddressOfEmptyProcedure)
  ; Set the pointer to the empty structure procedure.
  Protected *xlInfo.s_xListInfo
  If *xList = 0 : ProcedureReturn : EndIf
  *xlInfo = *xList\Left
  *xlInfo\AddressEmpty = AddressOfEmptyProcedure
EndProcedure
Procedure xlSetSortProcedure(*xList.s_xList, AddressOfSortProcedure)
  ; Set the pointer to the sorting procedure.
  Protected *xlInfo.s_xListInfo
  If *xList = 0 : ProcedureReturn : EndIf
  *xlInfo = *xList\Left
  *xlInfo\AddressSort = AddressOfSortProcedure
EndProcedure
Procedure xlSetAddBehavior(*xList.s_xList, AddChangesCurrent.b) ; If #True, the current node pointer will point to any newly added value.
  Protected *xlInfo.s_xListInfo
  If *xList = 0 : ProcedureReturn : EndIf
  *xlInfo = *xList\Left
  *xlInfo\AddChangesCurrent = AddChangesCurrent
EndProcedure
Procedure xlSetSortBehavior(*xList.s_xList, SortOnAdd.b) ; If #True, the list will be sorted every time an element is added.
  Protected *xlInfo.s_xListInfo
  If *xList = 0 : ProcedureReturn : EndIf
  *xlInfo = *xList\Left
  If *xlInfo\AddressSort : *xlInfo\AddCausesSort = SortOnAdd : EndIf
  ; Check To make sure our sort procedure address isn't 0.  That should be the case if we 
  ; haven't set our sort procedure yet with xlSetSortProcedure.
EndProcedure
Procedure xlCreate(*inStructureAddress, inStructureSize, AddressOfCopyProcedure)
  ;
  ; Call like xlCreate(@StructureVariable, SizeOf(Whatever_Your_Structure_Is_Named))
  ;
  Protected *xList.s_xList
  ;Protected *xlHold.s_classLines_Section
  Protected *xlInfo.s_xListInfo
  ;
  If *inStructureAddress = 0 Or inStructureSize = 0 Or AddressOfCopyProcedure = 0 : ProcedureReturn 0 : EndIf
  ; None of the passed values can be zero.  If so, exit.
  *xList = AllocateMemory(SizeOf(s_xList))
  ;
  *xlInfo = AllocateMemory(SizeOf(s_xListInfo))
  ; The fake root will not point to a parent node but will instead
  ; point to our information structure.  We'll use this to access
  ; optional parameters within the tree.
  *xList\Left = *xlInfo
  ; Our root should always point to our information structure on the left.
  *xlInfo\Count = 1
  ; Set the defaults for our information structure.  1 based.
  *xlInfo\CurrentIndex = 0
  ; Initially the current index is our only element.  Index 0.
  *xlInfo\Current = *xList
  ; Set our default Current pointer.  Points to the only node.
  *xlInfo\AddChangesCurrent = #True
  ; By default, when a new node is added we change our current pointer to the node.
  *xlInfo\AddCausesSort = #False
  ; Be default, we do not sort when a new element is added.
  *xlInfo\StructureSize = inStructureSize
  ; Hold the size of our value structure.
  *xlInfo\AddressCopy = AddressOfCopyProcedure
  ;
  *xList\Right = #xlEmpty
  ; The root is the only item so this points to nothing. 
  *xlHold = AllocateMemory(inStructureSize)
  ; Create our initial structure.
  CallFunctionFast(AddressOfCopyProcedure, *xlHold, *inStructureAddress)
  ; Copy the structure into our list item.
  *xList\Value = *xlHold
  ;
  ProcedureReturn *xList
  ; 
EndProcedure
Procedure xlIndex(*xList.s_xList, index, *inStructureAddress) ; Fills the structure with our value.
  ;
  ; Could be used like...
  ;
  ; MyTest.s_Test
  ; xlIndex(q, 0, @MyTest)
  ; Where 'q' is the pointer return from xlCreate and '0' is the first element in our list (zero based)
  ; 'MyTest' would hold the values from *PointerToHoldStructure
  ;
  Protected NodeCount
  ;
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    ;
    While *xList\Right <> #xlEmpty And index <> NodeCount
      ; Check to see if we've hit the last item or the index of the item we're looking for.
      *xList = *xList\Right
      ; Move right through our list.
      NodeCount + 1
    Wend
    ;
    If index = NodeCount
      CallFunctionFast(*xlInfo\AddressCopy, *inStructureAddress, *xList\Value)
      ; Copy our value into the passed parameter.
      ProcedureReturn #True
    EndIf
    ; Return the address of our value structure if we found the index.
    CallFunctionFast(*xlInfo\AddressEmpty, *inStructureAddress)
    ; Return an empty/bad structure.
  EndIf
  ;
  ProcedureReturn #False
  ; If we got here, we didn't find our item.
EndProcedure
Procedure xlSelectIndex(*xList.s_xList, index, *inStructureAddress) ; Fills the structure with our value and selects it as Current.
  ; See xlIndex for example usage.
  Protected NodeCount
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    ; Get our information structure.
    While *xList\Right <> #xlEmpty And index <> NodeCount
      ; Check to see if we've hit the last item or the index of the item we're looking for.
      *xList = *xList\Right
      ; Move right through our list.
      NodeCount + 1
    Wend
    ;
    If index = NodeCount
      ; We found the index we were looking for.
      *xlInfo\CurrentIndex = index
      ; Set the current index.
      *xlInfo\Current = *xList
      ; Select it...
      CallFunctionFast(*xlInfo\AddressCopy, *inStructureAddress, *xList\Value)
      ; Copy the item's value into our passed parameter.
      ProcedureReturn #True
      ; ...fill our structure and return True since everything went okay.
    EndIf
    ;
    CallFunctionFast(*xlInfo\AddressEmpty, *inStructureAddress)
    ; Return an empty structure since we didn't find what we were looking for.
  EndIf
  ;
  ProcedureReturn #False
  ; If we got here, we didn't find our item.
EndProcedure
Procedure xlValue(*xList.s_xList, *inStructureAddress) ; Returns the address for the value structure for the Current Node (last selected).
  ;
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    *xList = *xlInfo\Current
    CallFunctionFast(*xlInfo\AddressCopy, *inStructureAddress, *xList\Value) 
  EndIf
  ;
EndProcedure
Procedure xlAdd(*xList.s_xList, *inStructureAddress) ; Inserts an item at the end of our list.
  ;
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    ;
    *xlInfo = *xList\Left
    ;
    *xlHold = AllocateMemory(*xlInfo\StructureSize)
    ; Create our value structure in memory.
    CallFunctionFast(*xlInfo\AddressCopy, *xlHold, *inStructureAddress)
    ; Create a space for our value structure and copy our incoming values into it.
    If *xList\Right = #xlNull
      ; Add the item as our new root.
      *xList\Right = #xlEmpty
      ; As our list was empty and this was added as our new root, let the list know our
      ; next item is empty.
      *xList\Value = *xlHold
      ; Set our value structure.
      *xlInfo\Last = *xList
      ; Our last node is the root node.
      *xlInfo\Count = 1
      ; And now we have 1 item on our linked list.
      *xlInfo\CurrentIndex = 0
      ; This needs to be set regardless of AddChangesCurrent() behavior.
    Else
      If *xList\Right = #xlEmpty
        *xList\Right = xl_InsertItem(*xList, *xlHold, #False)
        *xlInfo\Count + 1
      Else
        ; Adding to the end.
        *xList = *xlInfo\Last 
        *xList\Right = xl_InsertItem(*xList, *xlHold, #False)
        *xlInfo\Count + 1
      EndIf
      *xlInfo\Last = *xList\Right
    EndIf
    ;
    If *xlInfo\AddChangesCurrent = #True
      *xlInfo\Current = *xList\Right
      *xlInfo\CurrentIndex = *xlInfo\Count-1
      ; Our count is 1 based.  Since we're adding at the end (or the root), subtract one.
    EndIf
    ;
    ProcedureReturn *xList\Right
    ;
  EndIf
  ;
EndProcedure
Procedure xlInsert(*xList.s_xList, *inStructureAddress, InsertAt) ; InsertAt=The position to insert.  0 for the root (first element)
  ;
  Protected NodeCount
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlRoot = *xList
    *xlInfo = *xList\Left
    ;
    *xlHold = AllocateMemory(*xlInfo\StructureSize)
    ; Create our value structure
    CallFunctionFast(*xlInfo\AddressCopy, *xlHold, *inStructureAddress)
    ; Create a space for our value structure and copy our incoming values into it.
    If *xList\Right = #xlNull
      ; Add the item as our new root.
      *xList\Right = #xlEmpty
      ; As our list was empty and this was added as our new root, let the list know our
      ; next item is empty.
      *xList\Value = *xlHold
      ; Set our the
      *xlInfo\Current = *xList
      ;
      *xlInfo\Last = *xList
      ;
      *xlInfo\Count = 1
      ; The count is now 1 sine we have an item on our list.
      *xlInfo\CurrentIndex = 0
      ; We need to set this regardless of the AddChangesCurrent() behavior.
    Else
      If InsertAt <> -1
        If InsertAt = 0
          ; Insert at the root.
          *xList\Right = xl_InsertItem(*xList, *xList\Value, #True)
          *xList\Value = *xlHold
          ; We're inserting at the root so insert the current
          ; root values as a new item and set the root to the
          ; node and the right value to the old node.
          *xlInfo\Count + 1
          ; Increment our node counter
          If *xlInfo\AddChangesCurrent = #True
            *xlInfo\Current = *xList
            *xlInfo\CurrentIndex = 0
          EndIf
          ;
          If *xlInfo\Last = *xList : *xlInfo\Last = *xList\Right : EndIf
          ;
        Else
          Repeat
            NodeCount + 1
            *xList = *xList\Right
            If NodeCount = InsertAt
              ; Found our insertion point.
              NodeCount = -1
              ; This will let the procedure know we found the insertion point.
              *xList\Right = xl_InsertItem(*xList, *xList\Value, #True)
              ; Since we're inserting, create a new item based on the current node and 
              ; set it to the current node's right pointer.  So if we're inserting '3'
              ; at index two (value 10) then we'll add a new node based on the existing
              ; '10' and set it to '10's right pointer.  Once we do that...
              *xList\Value = *xlHold
              ; ...we'll set the current node (which was 10) to our new value ('3', in our
              ; example).
              *xlInfo\Count + 1
              ; Increment our list counter.
              If *xlInfo\AddChangesCurrent = #True
                *xlInfo\Current = *xList
                *xlInfo\CurrentIndex = InsertAt
              EndIf
              ;
              If *xlInfo\Last = *xList : *xlInfo\Last = *xList\Right : EndIf
              ;
              Break
              ; And exit the loop.
            EndIf
          Until *xList\Right = #xlEmpty
          ; If we break at this point then we didn't find our insertion point - passed a value
          ; greater than the number of nodes.
          If NodeCount <> -1
            *xList\Right = xl_InsertItem(*xList, *xlHold, #False)
            ; We moved past our InsertAt position so add the value at the end.
            *xlInfo\Count + 1
            ;
            If *xlInfo\AddChangesCurrent = #True
              *xlInfo\Current = *xList\Right
              *xlInfo\CurrentIndex = *xlInfo\Count-1
            EndIf
            ;
            *xlInfo\Last = *xList\Right
            ;
          EndIf
        EndIf
      Else
        If *xList\Right = #xlEmpty
          *xList\Right = xl_InsertItem(*xList, *xlHold, #False)
          *xlInfo\Count + 1
          If *xlInfo\AddChangesCurrent = #True
            *xlInfo\Current = *xList\Right
            *xlInfo\CurrentIndex = *xlInfo\Count-1
          EndIf
          *xlInfo\Last = *xList\Right
        Else
          ; Adding to the end.
          *xList = *xlInfo\Last
          *xList\Right = xl_InsertItem(*xList, *xlHold, #False)
          *xlInfo\Count + 1
          If *xlInfo\AddChangesCurrent = #True
            *xlInfo\Current = *xList\Right
            *xlInfo\CurrentIndex = *xlInfo\Count-1
          EndIf
          *xlInfo\Last = *xList\Right
        EndIf
      EndIf
    EndIf
    ;
    ProcedureReturn *xList\Right
    ;
  EndIf
  ;
EndProcedure
Procedure xlUpdate(*xList.s_xList, *inStructureAddress, ElementIndex) ; Updates the structure stored at position 'ElementIndex'
  ;
  Protected NodeCount
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    ;
    If *xList\Right = #xlNull
      ; The list is uninitialized.  Don't update anything.
    Else
      If ElementIndex >= 0
        ; Make sure we passed a non-negative index.
        If ElementIndex = 0
          ; Update the root.
          CallFunctionFast(*xlInfo\AddressCopy, *xList\Value, *inStructureAddress)
          ; Copy the values from the passed structure (*inStructureAddress) into our
          ; existing value structure.
        Else
          Repeat
            NodeCount + 1
            *xList = *xList\Right
            If NodeCount = ElementIndex
              ; Found the node to update.
              CallFunctionFast(*xlInfo\AddressCopy, *xList\Value, *inStructureAddress)
              ; Copy the values from the passed structure (*inStructureAddress) into our
              ; existing value structure.
              Break
              ; And exit the loop.
            EndIf
          Until *xList\Right = #xlEmpty
          ; If we break at this point then we didn't find our update point - passed a value
          ; greater than the number of nodes.
        EndIf
      Else
        ; Passed -1 as our ElementIndex, update the current element.
        *xList = *xlInfo\Current
        ; Get our current element.
        CallFunctionFast(*xlInfo\AddressCopy, *xList\Value, *inStructureAddress)
        ; Copy the values from the passed structure (*inStructureAddress) into our
        ; existing value structure.
      EndIf
    EndIf
  EndIf
  ; 
EndProcedure
Procedure xlDestroy(*xList.s_xList) ; Completely destroys our list.
  Protected lHold
  Protected *xRoot.s_xList
  ;
  If *xList = 0 : ProcedureReturn : EndIf
  ; Make sure we passed a non null value.
  *xRoot = *xList
  If *xList\Right <> #xlEmpty
    *xList = *xList\Right
    While *xList\Right <> #xlEmpty
      ;
      lHold = *xList
      ; Hold our current node.
      FreeMemory(*xList\Value)
      ; Clear the memory for our value structure
      *xList = *xList\Right
      ; Get the next item.
      FreeMemory(lHold)
      ; Now clear our node from memory.
    Wend
    FreeMemory(*xList)
  EndIf
  FreeMemory(*xRoot\Left)
  ; Free the xList information structure.
  FreeMemory(*xRoot\Value)
  ; Clear the root's value structure.
  FreeMemory(*xRoot)
  ; Free the root.
EndProcedure
Procedure xlClear(*xList.s_xList) ; Clears all items from our list.
  ;
  Protected lHold
  ;
  Protected *xRoot.s_xList
  Protected *xlInfo.s_xListInfo
  ;
  If *xList = 0 : ProcedureReturn : EndIf
  ; Make sure we aren't trying to clear a null linked list.
  *xRoot = *xList
  ; Hold our root.
  *xlInfo = *xRoot\Left
  ; Hold our information structure.
  If *xList\Right <> #xlNull
    ; Make sure the list isn't already empty.
    If *xList\Right <> #xlEmpty
      ; This block will delete only the non-root nodes from memory.
      *xList = *xList\Right
      ; Move to the next item after our root, without deleting the root.
      While *xList\Right <> #xlEmpty
        lHold = *xList
        ; Hold the current node.
        FreeMemory(*xList\Value)
        ; Clear the memory for our value structure
        *xList = *xList\Right
        ; Update our pointer with the next node in our list.  This will not be empty since our While loop already checked.
        FreeMemory(lHold)
        ; Now delete our held node.
      Wend
      ; We exit here when we find the last non-empty node.
      FreeMemory(*xList)
      ; Delete the last non-empty, non-root node.
    EndIf
    ; The root is the only item left.
    FreeMemory(*xRoot\Value)
    ; Clear the value structure.  We'll recreate it when we add a value.
    *xRoot\Right = #xlNull
    ; Let the routines know that the list is unpopulated.
    *xlInfo\Count = 0
    ; No items left since we cleared everything.
    *xlInfo\Current = *xRoot
    ; Select our root item - just to be safe.
    *xlInfo\CurrentIndex = 0
    ; Same as above.
  EndIf
EndProcedure
Procedure xlRemove(*xList.s_xList, index) ; Removes a node from our list.
  ;
  Protected NodeCount
  ;
  Protected *xlRoot.s_xList
  Protected *xlNext.s_xList
  Protected *xlPrevious.s_xList
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlRoot = *xList
    *xlInfo = *xList\Left
    ;
    If *xList\Right = #xlNull
      ; The list is uninitialized.  Don't delete anything.
    Else
      If index >= 0
        ; Make sure we passed a non-negative index.
        If index = 0
          ; Delete the root.
          If *xList\Right <> #xlEmpty
            ; There are more items than the root item.
            *xlNext = *xlRoot\Right
            ; Get the address for the next item in our list.
            *xlRoot\Right = *xlNext\Right
            ; We can't just delete the node because then our root address would change.  That would cause
            ; the xList pointer to be invalidated (the xList pointer is the pointer returned from xlCreate())
            ; So, instead, we will copy the next node into our root, leaving the address the same.
            xl_SetLAddress(*xlNext\Right, *xlRoot)
            ; Make sure the "Next Node"'s next node is pointing back to our root item.
            FreeMemory(*xlRoot\Value)
            ; Since we're copying the 'Next Node' structure into our root structure, clear the old root value
            ; structure from memory.
            *xlRoot\Value = *xlNext\Value
            ; Set our root value pointer to our next item value pointer.
            If *xlInfo\Current = *xlNext : *xlInfo\Current = *xlRoot :  EndIf
            ; If our current node was the node after the root, set the current node to the root (which was the node after the root).
            If *xlInfo\CurrentIndex > 0 : *xlInfo\CurrentIndex -1 : EndIf
            ; Since we're deleting the root, all of the indexes will shift down.
            FreeMemory(*xlNext)
            ; Now, delete the next node since we've copied it's values to the root.  We effectively spliced it out. 
            *xlInfo\Count - 1
            ; Decrement our node counter 
          Else
            ; The root is the only item.
            FreeMemory(*xList\Value)
            ; Clear our value structure.
            *xList\Right = #xlNull
            ; Let the routines know that the list is unpopulated.
            *xlInfo\Count = 0
            ; Set our node count to 0 since there are no more items.
            *xlInfo\Current = *xList
            ; Set the current node pointer to our empty root.  Just to be safe.
            *xlInfo\CurrentIndex = 0
            ; Don't really need this here but might as well.  Should already be 0.
            *xlInfo\Last = *xList
            ; This won't matter since our 'Right' pointer is #xlNull but might as well.
          EndIf
        Else
          ; Passed a non-zero index value so we're going to delete something
          ; other than the root if we find it.
          Repeat
            NodeCount + 1
            *xList = *xList\Right
            If NodeCount = index
              ; Found the node to delete.
              If *xList\Right <> #xlEmpty
                ; There's another item after this.
                *xlNext = *xList\Right
                ; Grab our pointer 
                *xlPrevious = *xList\Left
                ;
                *xlNext\Left = *xlPrevious
                ; Dereference our current node by setting the next item's left pointer
                ; to the current node's left item.
                *xlPrevious\Right = *xlNext
                ; Make sure our previous item points to the next item (the one after the deleted item).
                If *xlInfo\Current = *xList : *xlInfo\Current = *xlPrevious : EndIf
                ; If we're deleting the currently selected node then set the previous node as current.
                If *xlInfo\CurrentIndex > index : *xlInfo\CurrentIndex - 1 : EndIf
                ; Since there is an item before this we only need to modify if the current index
                ; is set after the element to be deleted.  We'll decrement the current index 
                ; because we're deleting an item before it.
                FreeMemory(*xList\Value)
                ; Free our value structure from memory.
                FreeMemory(*xList)
                ; Free the node from memory.
                *xlInfo\Count - 1
                ; Decrement our item counter.
              Else
                ; This is the last item.
                *xlPrevious = *xList\Left
                ; Okay, yes, I'm cheating.  Rather than pointing to the next item in our list, it's
                ; pointing to the previous item.
                *xlPrevious\Right = #xlEmpty
                ; Dereference our current item.
                If *xlInfo\Current = *xList : *xlInfo\Current = *xlPrevious : EndIf
                ; If we're deleting the currently selected node then set the previous node as current. 
                If *xlInfo\CurrentIndex = index : *xlInfo\CurrentIndex -1 : EndIf
                ; If we're deleting the current index item, set it to the previous element.  Should 
                ; work okay since this is not the root and won't be less than 0.
                FreeMemory(*xList\Value)
                ; Free our value structure from memory.
                FreeMemory(*xList)
                ; And free it from memory.
                *xlInfo\Last = *xlPrevious
                ; Since we're deleting the last item in our linked list we need
                ; to update our Last element address.
                *xlInfo\Count - 1
                ; Decrement our item counter.
              EndIf
              ;
              Break
              ; And exit the loop.
            EndIf
          Until *xList\Right = #xlEmpty
          ; If we break at this point then we didn't find our index to delete - passed a value
          ; greater than the number of nodes.
        EndIf
      EndIf
    EndIf
  EndIf
  ;
EndProcedure
Procedure xlCount(*xList.s_xList)
  ; Returns a 1 based count of our items.
  Protected *xlInfo.s_xListInfo
  If *xList = 0 : ProcedureReturn 0 : EndIf
  ; Make sure we're checking a non-null linked list.
  *xlInfo = *xList\Left
  ProcedureReturn *xlInfo\Count
EndProcedure
Procedure xlReset(*xList.s_xList) ; Reset our Current pointer to -1 so xlNext will move to the root node.
  ;
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    *xlInfo\Current = -1
    ; No selected node.
    *xlInfo\CurrentIndex = -1
    ; No item is current.
  EndIf
  ;
EndProcedure
Procedure xlFirst(*xList.s_xList) ; Sets our current pointer to the first node and returns it's address...
  ; ...which, incidentally, is useless as the value passed to the procedure is the address ^_^
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    *xlInfo\Current = *xList
    *xlInfo\CurrentIndex = 0
  EndIf
  ;
  ProcedureReturn *xList
  ;
EndProcedure
Procedure xlLast(*xList.s_xList) ; Sets our current pointer to the last node and returns it's address
  ;
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    ;
    While *xList\Right <> #xlEmpty And *xList\Right <> #xlNull
      *xList = *xList\Right
    Wend
    ;
    *xlInfo\Current = *xList
    *xlInfo\CurrentIndex = *xlInfo\Count-1
    ; Since the count is 1 based and our index is not.
  EndIf
  ProcedureReturn *xList
  ;
EndProcedure
Procedure xlNext(*xList.s_xList) ; Sets our current pointer to the next node (after the current pointer).
  ;
  Protected *xlNode.s_xList
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    If *xlInfo\Current = -1
      ; We have no selected node.
      If *xList\Right = #xlNull
        ; Uninitialized list.  No next node.
        ProcedureReturn #False
      Else
        *xlInfo\Current = *xList
        ; Since our Current pointer is invalidated, move to the root item and return true.
        *xlInfo\CurrentIndex = 0
        ; And set the current index to the root.
        ProcedureReturn #True
        ; So return 
      EndIf
    Else
      *xlNode = *xlInfo\Current
    EndIf 
    ;
    If *xlNode\Right <> #xlEmpty And *xlNode\Right <> #xlNull
      ; Make sure there is a next items.
      *xlNode = *xlNode\Right
      ; Select it.
    Else
      ProcedureReturn #False
      ; Return False since there is no next element.
    EndIf
    ;
    *xlInfo\Current = *xlNode
    ;
    *xlInfo\CurrentIndex + 1
    ; We successfully moved right, increment our current index.
    ProcedureReturn #True
    ; There is a next node so return True.
  EndIf
  ;
  ProcedureReturn #False
  ; Return False since there is no next element.
EndProcedure
Procedure xlPrevious(*xList.s_xList) ; Sets our current pointer to the previous node (before the current pointer).
  ;
  Protected *xlNode.s_xList
  Protected *xlInfo.s_xListInfo
  ;
  If *xList
    *xlInfo = *xList\Left
    If *xlInfo\Current = -1
      ; No current node.
      ProcedureReturn #False
      ; We've reset the list - there is no previous item.
    Else
      *xlNode = *xlInfo\Current
    EndIf
    ;
    If *xList <> *xlNode
      ; Make sure the Current item is not the root item.  The root address is passed to xlPrevious.
      *xlNode = *xlNode\Left
      ; Select it.
    Else
      ProcedureReturn #False
      ; Return False since there is no previous element.
    EndIf
    ;
    *xlInfo\Current = *xlNode
    ;
    *xlInfo\CurrentIndex -1
    ; Decrement our current index.
    ProcedureReturn #True
    ; There is a next node so return True.
  EndIf
  ;
  ProcedureReturn #False
  ; Return False since there is no previous element.
EndProcedure
Procedure xlSort(*xList.s_xList) ; Call our custom sorting procedure.
  Protected *xlInfo.s_xListInfo
  If *xList
    *xlInfo = *xList\Left
    If *xlInfo\AddressSort : CallFunctionFast(*xlInfo\AddressSort,*xList) : EndIf
    ; Make sure we have set the sorting procedure before sorting.
  EndIf
EndProcedure
Procedure xlSwap(*xList.s_xList, Index01, Index02) ; Swap the two nodes.
  ;
  Protected lHold
  ; A variable to hold one of our values.
  If *xList
    If Index01 = Index02 : ProcedureReturn : EndIf
    ; Can't swap the same indexes.
    lAddress01 = xlAddress(*xList,Index01)
    ; Get the first address.
    If lAddress01 <> -1
      ; Make sure we found it.
      lAddress02 = xlAddress(*xList, Index02)
      ; Get the second address.
      If lAddress02 <> -1
        ; Make sure we found it.
        lHold = xl_Value(lAddress01)
        xl_SetValue(lAddress01, xl_Value(lAddress02))
        xl_SetValue(lAddress02, lHold)
      EndIf
    EndIf
  EndIf
EndProcedure
Procedure xlBookmark(*xList.s_xList) ; Will bookmark the current element.
  Protected *xlInfo.s_xListInfo
  If *xList
    *xlInfo = *xList\Left
    *xlInfo\Bookmark = *xlInfo\Current
  EndIf
EndProcedure
Procedure xlRecall(*xList.s_xList) ; Will return the current element to the bookmarked position.
  Protected *xlInfo.s_xListInfo
  If *xList
    *xlInfo = *xList\Left
    *xlInfo\Current = *xlInfo\Bookmark
    *xlInfo\CurrentIndex = xl_IndexFromAddress(*xList, *xlInfo\Current)
    ; Since we know the address of the current node, retrieve it's index.
  EndIf
EndProcedure
Procedure xlPosition(*xList.s_xList) ; Will return the index of the current element.
  ; Useful for returning the index while using xlNext/xlPrevious/xlLast.
  Protected *xlInfo.s_xListInfo
  If *xList
    *xlInfo = *xList\Left
    ProcedureReturn *xlInfo\CurrentIndex
  EndIf
EndProcedure
Procedure xlTestPrint(*xList.s_xList) ; Prints your list.  For debug purposes primarily.
  Protected lCount
  Protected sHold.s
  Protected *xlHold.s_Test
  If *xList\Right = #xlNull
    Debug "o-*"
  Else
    If *xList\Right = #xlEmpty
      *xlHold = *xList\Value
      ; Hold our value structure.
      Debug "o-'"+*xlHold\Named+"("+Str(*xlHold\lVar)+")'"
    Else
      Repeat
        *xlHold = *xList\Value
        If lCount = 0
          sHold = "*-->'"+*xlHold\Named+"("+Str(*xlHold\lVar)+")'"
        Else
          sHold = sHold+"-->'"+*xlHold\Named+"("+Str(*xlHold\lVar)+")'"
        EndIf 
        *xList = *xList\Right
        lCount + 1
      Until *xList\Right = #xlEmpty
      *xlHold = *xList\Value
      Debug sHold+"-->'"+*xlHold\Named+"("+Str(*xlHold\lVar)+")'-->*"
    EndIf
  EndIf
EndProcedure
Procedure xlLongPrint(*xList.s_xList) ; Prints your list.  For debug purposes primarily.
  Protected lCount
  Protected sHold.s
  If *xList\Right = #xlNull
    Debug "o-*"
  Else
    If *xList\Right = #xlEmpty
      Debug "o-'"+Str(PeekL(*xList\Value))+"'"
    Else
      Repeat
        If lCount = 0
          sHold = "o-->'"+Str(PeekL(*xList\Value))+"'"
        Else
          sHold = sHold+"-->'"+Str(PeekL(*xList\Value))+"'"
        EndIf 
        *xList = *xList\Right
        lCount + 1
      Until *xList\Right = #xlEmpty
      Debug sHold+"-->'"+Str(PeekL(*xList\Value))+"'-->*"
    EndIf
  EndIf
EndProcedure
;/
lHold = 69
q = xlCreate(@lHold, SizeOf(LONG), @xl_LongCopyStructure())
xlSetEmptyProcedure(q, @xl_LongEmptyStructure())
xlSetSortProcedure(q, @xl_LongSort())
xlLongPrint(q)
lHold = 26
xlAdd(q, @lHold)
lHold = 200221
xlAdd(q, @lHold)
lHold = 2
xlAdd(q, @lHold)
lHold = 999
xlAdd(q, @lHold)
lHold = 100
xlAdd(q, @lHold)
xlLongPrint(q)
xlSort(q)
xlLongPrint(q)
xlReset(q)
While xlNext(q)
  xlValue(q, @lHold)
  Debug Str(lHold)+" is in position "+Str(xlPosition(q))
Wend
xlRemove(q, 5)
xlLongPrint(q)
lHold = 4
xlAdd(q, @lHold)
xlLongPrint(q)
xlSort(q)
xlLongPrint(q)
xlDestroy(q)
;/
holdTest.s_Test
holdTest\Named = "Hi"
holdTest\lVar = 1
holdTest\bVar = 5
holdTest\sVar = "One"
;
q = xlCreate(@holdTest, SizeOf(s_Test), @xl_TestCopyStructure())
xlSetEmptyProcedure(q, @xl_TestEmptyStructure())
xlSetSortProcedure(q, @xl_TestSortBylVar())
;
holdTest\Named = "Ten"
holdTest\lVar = 10
xlAdd(q, @holdTest)
;
holdTest\Named = "Four"
holdTest\lVar = 4
xlAdd(q, @holdTest)
;
holdTest\Named = "Twenty"
holdTest\lVar = 20
xlAdd(q, @holdTest)
;
holdTest\Named = "Eight"
holdTest\lVar = 8
xlAdd(q, @holdTest)
;
xlTestPrint(q)
;
xlSort(q)
xlTestPrint(q)
;
xlSetSortProcedure(q, @xl_TestSortByNamed())
xlSort(q)
xlTestPrint(q)
;
xlDestroy(q)
Etc...

The code I'll post here is just storing string values but you could easily modify it to store whatever you want. Longs, floats.... structures.... Okay, so the way I hacked it together it's not as cool as the PB LinkedList structures where you reference them like "MyLinkedList()\StructureVar01" etc... but you can store them. And.... you can create a structure with a long that points to yet another linked list structure.

So, you can have a structure that stores information about your restaurant with a long that points to a linked list that dynamically holds info about your different foods. And each item in that linked list can have another long that points to yet another linked list that dynamically contains the ingredients for your food. Yes, I'm making that up as a horrible example.

Also - my code demonstrates a way you can store 'options' for each individual linked list. You could easily adapt this method to other code if you like it. Simple put - the root (head) of the linked list points to an informational structure. Since we call the linked list functions with the address of the root (passed from the xlCreate() function), we just have to grab the left item (pointer to the informational structure) and make changes.

Anyway, here's the code for simply storing strings. WARNING! Like I said earlier - it's unoptimized, ugly and there are barely any error checking code going on. I tried to comment it up so other people can see what's going on but .... ehhh... :D Be gentle :lol:

(Edit: Code removed to save space. See last post for link to new code using PureStorage)
Last edited by Xombie on Fri Dec 31, 2004 2:37 am, edited 1 time in total.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

And here's some very bad code on how you'd do it with structures. Don't forget that if you nest a linked list within another one and then delete an item from that first that you'd also need to FIRST delete it's pointer to the second linked list. Better memory handling that way.

(Edit: Code removed to save space. See my last post for download link using PureStorage)
Last edited by Xombie on Fri Dec 31, 2004 2:38 am, edited 1 time in total.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

By all means - pass around suggestions for improving the code ^_^ I know it needs it. I just wanted to share what I had so far.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

interesting, i played around with the idea and concept for a while... keep on posting...
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I'm recovering from the flu today so I thought I'd update this to work a little better with structures.

(Edit: Code removed to save space. Download using link in last post using PureStorage)

The difference is (aside from one or two minor tweaks) you have to create at least 2-3 procedures per linked list. You have to create a procedure to copy one structure to another, another procedure to copy empty/default your structure and (optional) a procedure to sort your list. I don't have a working sort procedure yet but perhaps in the future? I figure people have their own algorithms they like more than others so I put in a template but left it at that. And I show how you could sort by different fields if you wanted to. Once those three procedures are created, the linked list functions don't care what kind of structure you're using.

Again, it's light on error checking but I tried to put in a lot of comments to show what's happening so you can modify it to your own taste.
Last edited by Xombie on Fri Dec 31, 2004 2:39 am, edited 1 time in total.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I made another update to this code with some bug fixes and other alterations. The two main differences is that I store the last element address so it's quicker to simply add new items. The second is that I give examples on how to sort your linked list.

I'm also trying out this great PureStorage from Beach. Thanks :D The download link is at the end.

In my example I'm using a simple structure like....

Code: Select all

Structure s_Test
   Named.s
   lVar.l
   bVar.b
   sVar.s
EndStructure
You can then create several sorting routines. You can have it sort by 'Named' or 'lVar' or whichever field you want. You'll need two procedures per sorting field. One that the xlSort() procedure will call (as an example, TestSortBylVar) and then the actual sorting routine called by that routine (TestLVarQuickSort). It's not as difficult as it sound since you can use my examples as templates and simplpy change field names and structure names.

So in my example I do...

Code: Select all

holdTest.s_Test
holdTest\Named = "Hi"
holdTest\lVar = 1
holdTest\bVar = 5
holdTest\sVar = "One"
;
q.l = xlCreate(@holdTest, SizeOf(s_Test), @xl_TestCopyStructure())
xlSetEmptyProcedure(q, @xl_TestEmptyStructure())
xlSetSortProcedure(q, @xl_TestSortBylVar())
xlAdd(q, @holdTest)
You can see I've set our sorting procedure to sort using the lVar field in the structure. Now if I add a couple more elements (using the Named & lVar fields) - "Ten"/10, "Four"/4, "Twenty"/20 and "Eight"/8 and then call...

Code: Select all

xlPrint(q)
We'll see:

*-->'Hi( 1 )'-->'Ten( 10 )'-->'Four( 4 )'-->'Twenty( 20 )'-->'Eight( 8 )'-->*

And then calling...

Code: Select all

xlSort(q)
xlPrint(q)
Will show:

*-->'Hi( 1 )'-->'Four( 4 )'-->'Eight( 8 )'-->'Ten( 10 )'-->'Twenty( 20 )'-->*

And if we want to sort by the Named field we set up two more procedures to sort by that field and then call...

Code: Select all

xlSetSortProcedure(q, @xl_TestSortByNamed())
xlSort(q)
xlPrint(q)
To get:

*-->'Eight( 8 )'-->'Four( 4 )'-->'Hi( 1 )'-->'Ten( 10 )'-->'Twenty( 20 )'-->*

The code I'm linking below shows how to sort by either the lVar or Named fields. I'm sure it's not the fastest way to do it but it works and might give someone else an idea of what to do from there.

Hope it's useful! :D

Updated Linked List code: xList-Structure.pb
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I have some more bug fixes and an xlUpdate() procedure to post but I left the code at work so I'll have to wait until tomorrow :D A little bit of cleaning but also fixing how the sort handles the current element pointer.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I made another update. From the header of the *.pb file:
1. Updated to include xlUpdate() function to update the value for an existing element.
2. Cleaned xlAdd() and xlAddAt() functions to remove variables not used.
3. Fixed how the Current field is used when sorting.
4. Fixed xlValue() to actually do what it's supposed to do.
5. Added xlBookmark() to remember a position and xlRecall() to go back to that bookmark.
I also changed xlAddAt() to xlInsert(). I kinda ran out of ideas of what to name these procedures. Like I don't think xlIndex() is a good name for what it does. However, I can't think of proper names so I welcome suggestions.

I've also changed the QuickSort code template to handle the Current element pointer when sorting so be sure to check that out when implementing. I need to fix it up to work with the new Bookmark function as well.

Download here: xList-Structure.pb
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I've updated the code a little bit more. From the notes...
1/27/2005
1. Added xlPosition() to return the index of the current element. Maybe I'll think about adding a Position variable to each element to speed this part up? More memory usage that way, however.
2. Fixed up a few more minor bugs.
1/24/2005
1. Put in some minor bug checking to make sure a non-zero *xList value is passed to our public functions. Still some to add and other things to check.
2. Changed AddAt() to Insert(). Actually might have been in a previous example.
1/7/2005
1. Some bug fixes. For example, deleting the last item wasn't setting the Last item address.
2. Additional examples - how to have a linked list of just long values.
3. Updated the QuickSort routines. Moved the Current/Bookmark swapping routines
to xl_Swap() as an 'optional' parameter. Also, listing an example of how you would sort a linked list of long values. You'll want to update any of your older Quicksort routines to work with this.
Still a work in progress but I'm using it in my main project and it's working well so far.

Is anyone else using it? I'm curious to know what y'all think.

PureStorage seems broken so I'm using a different download location.

Download here: Download xList.pb
PolyVector
Enthusiast
Enthusiast
Posts: 499
Joined: Wed Sep 17, 2003 9:17 pm
Location: Southern California
Contact:

Post by PolyVector »

Nice job, I suppose my xList is obsolete now :shock:... I didn't even think of implimenting copy procedures....
Hell, we just need PB to be OO...hehehe
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

You're too kind :D

However, my version isn't threadsafe and yours is. Also, mine is still buggy and most likely darn slow.

OO would be interesting in PB but I'm actually finding I don't need it that much. I'm building a new version of this old beta program I made in VB and it uses classes. I was fearful that I wouldn't be able to do everything in PB but with addresses of procedures and pointers I've been able to do everything I needed. So far :D

And using a copy procedure in my code just made sense. Since you can't use copymemory to copy strings I thought a custom copy procedure would be best. Also, it comes in handy for a few others things.
dracflamloc
Addict
Addict
Posts: 1648
Joined: Mon Sep 20, 2004 3:52 pm
Contact:

Post by dracflamloc »

The nice thing if PB implemented OO capabilities, would be for people using TailBite or converting C++ libraries. It'd eliminate the need for tons of wrapper functions, and let people write more robust PB libs right in PB itself.
Post Reply