An alternative linkedlist

Developed or developing a new product in PureBasic? Tell the world about it.
Dräc
Enthusiast
Enthusiast
Posts: 150
Joined: Sat Oct 09, 2004 12:10 am
Location: Toulouse (France)
Contact:

An alternative linkedlist

Post by Dräc »

A textbook case: Here an alternative linkedlist that can be embedded into structures (needed for my project)
The solution selected is to keep a copy of the data into the linkedlist and to exclude any String type.
The goal is to be as close as possible to the standard PB linkedlist
Notice the associated ForEach:Next macro :wink:

Need some help to validate this.

Code: Select all

;=====================================================
;  LinkedListEx
;
;  An alternative linked-list.
;  This linked-list can be embedded into a structure
;
;  Dräc - Sept 2007
;=====================================================

Structure LLEx_Element 
  *next.LLEx_Element 
  *prev.LLEx_Element
EndStructure 

Structure LinkedListEx 
  *first.LLEx_Element 
  *current.LLEx_Element
  *last.LLEx_Element
  Index.l 
  Count.l
  DataLenght.l
EndStructure

;------------------------------- NewListEx ------------------------------
;
; NewListEx allows to declare a new dynamic linked list. Each element of
; the list is allocated dynamically.
; There are no element limits, so there can be as many as needed.
; A such list can contain any:
; - Variables standard except String type,
; - Structured type without any String field
; - Adress
;
; INPUT:
; The size of the Data (use SizeOf() if necessary)
;------------------------------------------------------------------------
Procedure NewListEx(DataLenght.l)
  *list.LinkedListEx = AllocateMemory(SizeOf(LinkedListEx)) 
  *list\Index = -1 
  *list\Count =  0
  *list\DataLenght = DataLenght
  ProcedureReturn *list 
EndProcedure

;------------------------------ ClearListEx -----------------------------
;
; Clears all the elements in this list and releases their memory.
; After this call the list is still usable, but the list is empty
; (i.e. there are no elements in it).
;------------------------------------------------------------------------
Procedure ClearListEx(*list.LinkedListEx)
  With *list
    \current = \first
    While \current
      *this = \current
      \current = \current\next
      FreeMemory(*this)
    Wend
    \first = #Null
    \current = #Null
    \last = #Null
    \Index = 0
    \Count = 0 
  EndWith
EndProcedure

;---------------------------- DeleteListEx ------------------------------
;
; Clears all the elements in this list and releases their memory
; After this call the list is deleted and its memory free
;------------------------------------------------------------------------
Procedure DeleteListEx(*list.LinkedListEx) 
  ClearListEx(*list)
  FreeMemory(*list)
EndProcedure

;---------------------------- AddElementEx ------------------------------
;
; Adds a new element after the current element or
; as the first item in the list if there are no elements in it.
; This new element becomes the current element of the list.
;
; Copy data from the memory source adress given by *adress into
; the new element
;
; Return the new element adress or zero if the new element could not be
; created
;
; INPUT:
; The adress of the data to store
;------------------------------------------------------------------------
Procedure AddElementEx(*list.LinkedListEx, *adress) 
  With *list
    *this.LLEx_Element = AllocateMemory(SizeOf(LLEx_Element)+\DataLenght)
    If *this

      ;If there is an element in the list, add after the current element
      If \Count
        *this\prev = \current
        If \current\next
          *this\next = \current\next
          \current\next\prev = *this
        Else; the new element is the last element
          \last = *this
        EndIf
        \current\next = *this
        \current = *this
        
      Else; No element in the list
        \first = *this
        \current = *this
        \last = *this
      EndIf
      
      CopyMemory(*adress, *this+SizeOf(LLEx_Element), \DataLenght)
      \Count + 1  
      \Index + 1
    EndIf
    ProcedureReturn *this
  EndWith
EndProcedure

;-------------------------- InsertElementEx -----------------------------
;
; Inserts a new empty element before the current element or
; at the start of the list if the list is empty.
; This new element becomes the current element of the list.
;
; Copy data from the memory source adress given by *adress into
; the new element
;
; Return the new element adress or zero if the new element could not be
; created
;
; INPUT:
; The adress of the data to store
;------------------------------------------------------------------------
Procedure InsertElementEx(*list.LinkedListEx, *adress)
  With *list
    *this.LLEx_Element = AllocateMemory(SizeOf(LLEx_Element)+\DataLenght)
    If *this

      ;If there is an element in the list, add before the current element
      If \Count
        *this\next = \current        
        If \current\prev
          *this\prev = \current\prev
          \current\prev\next = *this
        Else; the new element is the firt element
          \first = *this
        EndIf
        \current\prev = *this
        \current = *this
        
      Else; No element in the list
        \first = *this
        \current = *this
        \last = *this
        \Index + 1
      EndIf
      
      CopyMemory(*adress, *this+SizeOf(LLEx_Element), \DataLenght)
      \Count + 1
      
      
    EndIf
    ProcedureReturn *this
  EndWith 
EndProcedure

;-------------------------- DeleteElementEx -----------------------------
;
; Remove the current element from the list. After this call, the new
; current element is the previous element (the one before the deleted
; element). If that element does not exist (in other words, you deleted
; the first element in the list) then the new current element is the
; next element
;
; Return the current element adress or zero if the list is empty
;------------------------------------------------------------------------
Procedure DeleteElementEx(*list.LinkedListEx)
  With *list
  
      ;If there is an element in the list, delete the current element
      If \Count
      
        *this.LLEx_Element = \current
        
        ;If the current element has a next one, update next link
        If *this\next
          *this\next\prev = *this\prev
          \current = *this\next
          \Index + 1
        Else; update the list last element link
          \last = *this\prev
        EndIf
        
        ;If the current element has a previous one, update previous link
        If *this\prev
          *this\prev\next = *this\next
          \current = *this\prev
        Else; update the list first element link
          \first = *this\next
        EndIf
        \Index - 1

        
        \Count - 1      
        FreeMemory(*this)
        
      EndIf
      ProcedureReturn *this
  EndWith
EndProcedure

;---------------------------- NextElementEx -----------------------------
;
; Moves from the current element to the next element in the list
; The next element can't go below the last element.
;
; Return the current element adress.
;------------------------------------------------------------------------
Procedure NextElementEx(*list.LinkedListEx)
  With *list
      If \current\next
        \current = \current\next
        \Index + 1
      EndIf
      ProcedureReturn \current
  EndWith
EndProcedure

;-------------------------- PreviousElementEx ---------------------------
;
; Moves from the current element to the previous element in the list
; The previous element can't go above the first element. 
;
; Return the current element adress.
;------------------------------------------------------------------------
Procedure PreviousElementEx(*list.LinkedListEx)
  With *list
      If \current\prev
        \current = \current\prev
        \Index - 1
      EndIf
      ProcedureReturn \current
  EndWith
EndProcedure

;---------------------------- FirstElementEx ----------------------------
;
; Changes the current list element to the first list element.
;
; Return the current element adress.
;------------------------------------------------------------------------
Procedure FirstElementEx(*list.LinkedListEx)
  With *list
    \current = \first
    \Index = 0
    ProcedureReturn \first
  EndWith
EndProcedure

;---------------------------- CurrentElement ----------------------------
;
; Return the current element adress.
;------------------------------------------------------------------------
Macro CurrentElementEx(list)
  list\current
EndMacro

;----------------------------- LastElementEx ----------------------------
;
; Changes the current list element to the last list element.
;
; Return the current element adress.
;------------------------------------------------------------------------
Procedure LastElementEx(*list.LinkedListEx)
  With *list
    \current = \last
    \Index = \Count-1
    ProcedureReturn \last
  EndWith
EndProcedure

;------------------------------ ListIndexEx -----------------------------
;
; Return the position of the current element in the list, considering
; that the first element is at the position 0. This function is very fast
; and can be used a lot without performance issue (it doesn't iterate the
; list but uses a cached value).
;------------------------------------------------------------------------
Procedure.l ListIndexEx(*list.LinkedListEx)
  With *list
    ProcedureReturn \Index
  EndWith
EndProcedure

;------------------------------ CountListEx -----------------------------
;
; Counts how many elements there are in the linked list. It does not
; change the current element. This command is very fast (it doesn't
; iterates all the list but uses a cached result) and can be safely used
; to determine if a list is empty or not.
;------------------------------------------------------------------------
Procedure.l CountListEx(*list.LinkedListEx)
  With *list
    ProcedureReturn \Count
  EndWith
EndProcedure
; 
; Procedure ResetListEx(*list.LinkedListEx)
;   With *list
;     \current = \first
;     \Index = 0    
;   EndWith
; EndProcedure

;---------------------------- SelectElementEx ---------------------------
;
; Change the current list element to the element at the specified
; position. The first list element is at position 0, the next at position
; 1 and so on. This is very useful if you want to jump directly to a
; specific position in the klist rather than move through each item
; in turn.
;
; INPUT:
; Index of the element in the list
;------------------------------------------------------------------------
Procedure SelectElementEx(*list.LinkedListEx, Index.l)
  With *list
    If Index >= 0 And Index < \Count
      
      offset.l = Index - \Index
      If offset > 0
        While \Index < Index
          NextElementEx(*list)
        Wend
      Else
        While \Index > Index
          PreviousElementEx(*list)
        Wend    
      EndIf
      
    EndIf
    ProcedureReturn \current
  EndWith
EndProcedure

;----------------------------- SwapElementsEx ---------------------------
;
; Swaps 2 elements of the specified list. The '*FirstElement' and
; '*SecondElement' parameters must be pointers to valid list elements.
; This function is very useful if you want to sort or reorganize quickly
; a list. It should be used with care and only by advanced users.
;
; INPUT:
; Adresses of the two Elements to swap
;------------------------------------------------------------------------
Procedure SwapElementsEx(*list.LinkedListEx, *FirstElement.LLEx_Element, *SecondElement.LLEx_Element)
  With *list    
    ;update the current pointer of the list (Index not change)
    If *FirstElement = \current
      \current = *SecondElement
    ElseIf *SecondElement = \current
      \current = *FirstElement
    EndIf
    
    ;update first and last pointers of the list
    If *FirstElement = \first
      \first = *SecondElement
      If *SecondElement = \last
        \last = *FirstElement
            ;update neigbors
;            *FirstElement\prev\next = *SecondElement
            *FirstElement\next\prev = *SecondElement
            *SecondElement\prev\next = *FirstElement
;            *SecondElement\next\prev = *FirstElement
      Else
            ;update neigbors
;            *FirstElement\prev\next = *SecondElement
            *FirstElement\next\prev = *SecondElement
            *SecondElement\prev\next = *FirstElement
            *SecondElement\next\prev = *FirstElement
      EndIf
      
    ElseIf *FirstElement = \last
      \last = *SecondElement
      If *SecondElement = \first
        \first = *FirstElement
            ;update neigbors
            *FirstElement\prev\next = *SecondElement
;            *FirstElement\next\prev = *SecondElement
;            *SecondElement\prev\next = *FirstElement
            *SecondElement\next\prev = *FirstElement
      Else
            ;update neigbors
             *FirstElement\prev\next = *SecondElement
;            *FirstElement\next\prev = *SecondElement
            *SecondElement\prev\next = *FirstElement
            *SecondElement\next\prev = *FirstElement
      EndIf
      
    ElseIf *SecondElement = \first
      \first = *FirstElement
            *FirstElement\prev\next = *SecondElement
            *FirstElement\next\prev = *SecondElement
;            *SecondElement\prev\next = *FirstElement
            *SecondElement\next\prev = *FirstElement      
    ElseIf *SecondElement = \last
      \last = *FirstElement
            *FirstElement\prev\next = *SecondElement
            *FirstElement\next\prev = *SecondElement
            *SecondElement\prev\next = *FirstElement
;            *SecondElement\next\prev = *FirstElement  
    Else
            *FirstElement\prev\next = *SecondElement
            *FirstElement\next\prev = *SecondElement
            *SecondElement\prev\next = *FirstElement
            *SecondElement\next\prev = *FirstElement 
    EndIf
    
    ;swap elements
    Element.LLEx_Element\prev = *FirstElement\prev
    Element.LLEx_Element\next = *FirstElement\next
    *FirstElement\prev = *SecondElement\prev
    *FirstElement\next = *SecondElement\next            
    *SecondElement\prev = Element\prev
    *SecondElement\next = Element\next
  EndWith
EndProcedure

;------------------------- ChangeCurrentElementEx -----------------------
;
; Changes the current element of the specified list to the given new
; element. The new element must be a pointer to another element which
; exists in this list. This function is very useful if you want to
; "remember" an element, and restore it after performing other processing.
; It should be used with care and only by advanced users.
;
; INPUT:
; Adresses of the Elements to find
;------------------------------------------------------------------------
Procedure ChangeCurrentElementEx(*list.LinkedListEx, *Element.LLEx_Element)
  With *list    
    If \current <> *Element
      \current = \first
      \Index = 0
      While \current <> *Element
        NextElementEx(*list)
      Wend
    EndIf
  EndWith
EndProcedure

;---------------------------- GetCurrentData ----------------------------
;
; Return the adress of the data that the current element contains.
;------------------------------------------------------------------------
Macro GetCurrentData(list)
  list\current+SizeOf(LLEx_Element)
EndMacro

;--------------------------- List loop Macro ----------------------------
;
; ForEach loops through all elements in the specified linked list starting
; from the first element up to the last.
; If the list is empty, ForEach : Next exits immediately.
;------------------------------------------------------------------------
Macro ForEach(list)
  ; move the current element to the first element
  list\current = list\first
  list\Index = 0
  ; loop through all elements and exit after the last
  While list\current
EndMacro

Macro Next(list)
  ; Next element of the list
  list\current = list\current\next
  list\Index + 1
  Wend
  ;if no current element (all elements crossed), move it to the last one
  If list\current = 0
    list\current = list\last
    list\Index = list\Count -1
  EndIf
EndMacro
Follows a test code

Code: Select all

  IncludeFile "LinkedListEx.pbi"
  
  Structure MyStructure
    field1.q
    field2.b
  EndStructure

  *MyStructures.LinkedListEx = NewListEx(SizeOf(MyStructure)); La liste pour stocker les éléments
  
  MyStruct.MyStructure\field1 = 10
  MyStruct.MyStructure\field2 = 3    
  AddElementEx(*MyStructures, MyStruct)
    
  MyStruct.MyStructure\field1 = 20
  MyStruct.MyStructure\field2 = 4
  AddElementEx(*MyStructures, @MyStruct)

  MyStruct.MyStructure\field1 = 30
  MyStruct.MyStructure\field2 = 5
  AddElementEx(*MyStructures, @MyStruct)  

 ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
 Next(*MyStructures)
  
  Debug "FirstElement"
  FirstElementEx(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  Debug "LastElement"    
  LastElementEx(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  Debug "------------------------"  
  Debug "DeleteElementEx"    
  FirstElementEx(*MyStructures)
  NextElementEx(*MyStructures)
  DeleteElementEx(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
    
    Debug "LastElement"    
    *MyStruct.MyStructure = *MyStructures\last+SizeOf(LLEx_Element)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  
    Debug CountListEx(*MyStructures)
    Debug ListIndexEx(*MyStructures)
    Debug ""  
 ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
 Next(*MyStructures)
  
  Debug "------------------------"  
  Debug "AddElementEx"    
  MyStruct.MyStructure\field1 = 100
  MyStruct.MyStructure\field2 = 30
  FirstElementEx(*MyStructures)
  AddElementEx(*MyStructures, @MyStruct)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)

    Debug "LastElement"     
    *MyStruct.MyStructure = *MyStructures\last+SizeOf(LLEx_Element)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  
    Debug CountListEx(*MyStructures)
    Debug ListIndexEx(*MyStructures)
    Debug ""  
ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
Next(*MyStructures)
  
  Debug "------------------------"  
  Debug "InsertElementEx"    
  MyStruct.MyStructure\field1 = 66
  MyStruct.MyStructure\field2 = 64
  InsertElementEx(*MyStructures, @MyStruct)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
    
    Debug "LastElement"    
    *MyStruct.MyStructure = *MyStructures\last+SizeOf(LLEx_Element)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  
    Debug CountListEx(*MyStructures)
    Debug ListIndexEx(*MyStructures)
    Debug ""  
ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
Next(*MyStructures)
  
  Debug "------------------------"  
  Debug "InsertElementEx"    
  MyStruct.MyStructure\field1 = 200
  MyStruct.MyStructure\field2 = 11
  FirstElementEx(*MyStructures)
  InsertElementEx(*MyStructures, @MyStruct)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
    
    Debug "FistElement"    
    *MyStruct.MyStructure = *MyStructures\first+SizeOf(LLEx_Element)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
    Debug "Count/Index"
    Debug CountListEx(*MyStructures)
    Debug ListIndexEx(*MyStructures)
    Debug ""  
ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
 Next(*MyStructures)
  
  Debug "------------------------"  
  Debug "SelectElementEx"
  FirstElementEx(*MyStructures)
  NextElementEx(*MyStructures)
  NextElementEx(*MyStructures)
  
  SelectElementEx(*MyStructures, 0)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  
  Debug "------------------------"  
  Debug "SelectElementEx"
  FirstElementEx(*MyStructures)
  NextElementEx(*MyStructures)
  NextElementEx(*MyStructures)
  
  SelectElementEx(*MyStructures, 4)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
   
  Debug "------------------------"  
 Debug "SwapElements"
  *El1 = FirstElementEx(*MyStructures)
  *El2 = LastElementEx(*MyStructures)
  
  SwapElementsEx(*MyStructures, *El1, *El2)

 ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
 Next(*MyStructures)
     
  Debug "------------------------"  
  Debug "SwapElements"
  *El1 = SelectElementEx(*MyStructures, 3)
  *El2 = SelectElementEx(*MyStructures, 1)
  
  SwapElementsEx(*MyStructures, *El1, *El2)
 ; Test links in on way
 ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
 Next(*MyStructures)
  
  Debug ""

;Test links in the others way
  LastElementEx(*MyStructures)
  While *MyStructures\current
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  *MyStructures\current = *MyStructures\current\prev
  *MyStructures\Index - 1
  Wend
  *MyStructures\current = *MyStructures\first
  *MyStructures\Index = 0
  
  
     
  Debug "------------------------"  
  Debug "ClearListEx"
  ClearListEx(*MyStructures)

    Debug "Count/Index"
    Debug CountListEx(*MyStructures)
    Debug ListIndexEx(*MyStructures)
    
 ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
 Next(*MyStructures)

  Debug "------------------------"  
  Debug "AddElementEx"
  MyStruct.MyStructure\field1 = 10;"Tom"
  MyStruct.MyStructure\field2 = 3    
  AddElementEx(*MyStructures, @MyStruct)
    
  MyStruct.MyStructure\field1 = 20;"Dick"
  MyStruct.MyStructure\field2 = 4
  AddElementEx(*MyStructures, @MyStruct)

  MyStruct.MyStructure\field1 = 30;"Harry"
  MyStruct.MyStructure\field2 = 5
  AddElementEx(*MyStructures, @MyStruct)  

    Debug "Count/Index"  
    Debug CountListEx(*MyStructures)
    Debug ListIndexEx(*MyStructures)
    Debug ""
    
  ForEach(*MyStructures)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  Next(*MyStructures)
  
  Debug "------------------------"  
 Debug "ChangeCurrentElementEx"
  *El1 = SelectElementEx(*MyStructures, 0)
    *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
  LastElementEx(*MyStructures)
  
  ChangeCurrentElementEx(*MyStructures, *El1)
      *MyStruct.MyStructure = GetCurrentData(*MyStructures)
    Debug Str(*MyStruct\field1)+"/"+Str(*MyStruct\field2)
Dräc