Code archives/Miscellaneous/Linked List with Iterator

This code has been declared by its author to be Public Domain code.

Download source code

Linked List with Iterator by Yasha2011
This is just an extremely simple list container that allows you to store lists of items without resorting to either 1) the builtin Type lists, or 2) ugly workarounds like extendable banks or "next object" fields.

The interface is, for the most part, based on BlitzMax's BRL.LinkedList module, in order to make usage as simple as possible (the code itself is all original, so it's all safely PD), although there are a few changes to the names. The most notable missing feature is a Sort function, since Blitz Classic doesn't support function pointers (without hacks).

Since Blitz Classic also doesn't have any way to provide EachIn functionality to a class (...not having classes), looping is handled by a construct based on this recent forum discussion, which uses Iterator objects (similar to the way it's done in C++ or old-style Java). Example usage:

Local l.LList = CreateList()

...

Local i.Iterator = GetIterator(l)
While EachIn(i)
    Local elem.SomeType = Object.SomeType(i\Value)
    ...
    If ... Then IteratorBreak(i):Exit

    If ... Then IteratorRemove(i)
Wend


GetIterator obtains an iterator for the specified list. With each loop, EachIn gets the next value in the list (which is always an integer, which usually you'll want to use with Object/Handle) and makes it available through iterator\Value. At the end of the loop the iterator is disconnected from the list, so in most programs you don't need to think about cleaning up after it; however, if you want to break out of the loop you need to manually disconnect it with IteratorBreak before exiting (or just Delete it).

During an EachIn loop, you can remove an item from the list being iterated over with IteratorRemove, which will adjust the list so that the next iteration of the loop will work as expected. If the element was storing an object, you'll need to clean it up manually as B3D provides no garbage collection, and the list doesn't know what the values it holds represent (and is thus not able to free them for you).

This entry duplicates functionality that has almost certainly been submitted many times before, and a linked list is so pathetically trivial that even a beginner could make this with their eyes shut. With this in mind, the reason for posting this library is mainly so that I can post later Code Archives entries that just Include "LList.bb" and not have to include the same boilerplate rubbish each time!
; Double-linked list container class
;====================================

; with thanks to MusicianKool, for concept and issue fixes


Type LList
	Field head_.ListNode
	Field tail_.ListNode
End Type

Type ListNode
	Field pv_.ListNode
	Field nx_.ListNode
	Field Value
End Type

Type Iterator
	Field Value
	Field l_.LList
	Field cn_.ListNode, cni_
End Type


;Create a new LList object
Function CreateList.LList()
	Local l.LList = New LList
	
	l\head_ = New ListNode
	l\tail_ = New ListNode
	
	l\head_\nx_ = l\tail_		;End caps
	l\head_\pv_ = l\head_		;These make it more or less safe to iterate freely
	l\head_\Value = 0
	
	l\tail_\nx_ = l\tail_
	l\tail_\pv_ = l\head_
	l\tail_\Value = 0
	
	Return l
End Function

;Free a list and all elements (not any values)
Function FreeList(l.LList)
	ClearList l
	Delete l\head_
	Delete l\tail_
	Delete l
End Function

;Remove all the elements from a list (does not free values)
Function ClearList(l.LList)
	Local n.ListNode = l\head_\nx_
	While n <> l\tail_
		Local nx.ListNode = n\nx_
		Delete n
		n = nx
	Wend
	l\head_\nx_ = l\tail_
	l\tail_\pv_ = l\head_
End Function

;Count the number of elements in a list (slow)
Function ListLength(l.LList)
	Local i.Iterator = GetIterator(l), elems
	While EachIn(i)
		elems = elems + 1
	Wend
	Return elems
End Function

;Return True if a list contains a given value
Function ListContains(l.LList, Value)
	Return (ListFindNode(l, Value) <> Null)
End Function

;Create a linked list from the intvalues in a bank (slow)
Function ListFromBank.LList(bank)
	Local l.LList = CreateList()
	Local size = BankSize(bank), p
	
	For p = 0 To size - 4 Step 4
		ListAddLast l, PeekInt(bank, p)
	Next
	
	Return l
End Function

;Create a bank containing all the values in a list (slow)
Function ListToBank(l.LList)
	Local size = ListLength(l) * 4
	Local bank = CreateBank(size)
	
	Local i.Iterator = GetIterator(l), p = 0
	While EachIn(i)
		PokeInt bank, p, i\Value
		p = p + 4
	Wend
	
	Return bank
End Function

;Swap the contents of two list objects
Function SwapLists(l1.LList, l2.LList)
	Local tempH.ListNode = l1\head_, tempT.ListNode = l1\tail_
	l1\head_ = l2\head_
	l1\tail_ = l2\tail_
	l2\head_ = tempH
	l2\tail_ = tempT
End Function

;Create a new list containing the same values as the first
Function CopyList.LList(lo.LList)
	Local ln.LList = CreateList()
	Local i.Iterator = GetIterator(lo) : While EachIn(i)
		ListAddLast ln, i\Value
	Wend
	Return ln
End Function

;Reverse the order of elements of a list
Function ReverseList(l.LList)
	Local n1.ListNode, n2.ListNode, tmp.ListNode
	
	n1 = l\head_
	n2 = l\head_\nx_
	
	While n1 <> l\tail_
		n1\pv_ = n2
		tmp = n2\nx_
		n2\nx_ = n1
		n1 = n2
		n2 = tmp
	Wend
	
	tmp = l\head_
	l\head_ = l\tail_
	l\tail_ = tmp
	
	l\head_\pv_ = l\head_
	l\tail_\nx_ = l\tail_
End Function

;Search a list to retrieve the first node with the given value
Function ListFindNode.ListNode(l.LList, Value)
	Local n.ListNode = l\head_\nx_
	
	While n <> l\tail_
		If n\Value = Value Then Return n
		n = n\nx_
	Wend
	
	Return Null
End Function

;Append a value to the end of a list (fast) and return the node
Function ListAddLast.ListNode(l.LList, Value)
	Local n.ListNode = New ListNode
	
	n\pv_ = l\tail_\pv_
	n\nx_ = l\tail_
	n\Value = Value
	
	l\tail_\pv_ = n
	n\pv_\nx_ = n
	
	Return n
End Function

;Attach a value to the start of a list (fast) and return the node
Function ListAddFirst.ListNode(l.LList, Value)
	Local n.ListNode = New ListNode
	
	n\pv_ = l\head_
	n\nx_ = l\head_\nx_
	n\Value = Value
	
	l\head_\nx_ = n
	n\nx_\pv_ = n
	
	Return n
End Function

;Remove the first occurence of the given value from a list
Function ListRemove(l.LList, Value)
	Local n.ListNode = ListFindNode(l, Value)
	If n <> Null Then RemoveListNode n
End Function

;Remove a node from a list
Function RemoveListNode(n.ListNode)
	n\pv_\nx_ = n\nx_
	n\nx_\pv_ = n\pv_
	Delete n
End Function

;Return the value of the element at the given position from the start of the list,
;or backwards from the end of the list for a negative index
Function ValueAtIndex(l.LList, index)
	Local n.ListNode = ListNodeAtIndex(l, index)
	If n <> Null Then Return n\Value : Else Return 0
End Function

;Return the ListNode at the given position from the start of the list, or backwards
;from the end of the list for a negative index, or Null if invalid
Function ListNodeAtIndex.ListNode(l.LList, index)
	Local e, n.ListNode
	
	If index >= 0
		n = l\head_
		For e = 0 To index
			n = n\nx_
		Next
		If n = l\tail_ Then n = Null	;Beyond the end of the list - not valid
		
	Else	;Negative index - count backward
		n = l\tail_
		For e = 0 To index Step -1
			n = n\pv_
		Next
		If n = l\head_ Then n = Null	;Before the start of the list - not valid
		
	EndIf
	
	Return n
End Function

;Replace a value at the given position (added by MusicianKool)
Function ReplaceValueAtIndex(l.LList,index,value)
	Local n.ListNode = ListNodeAtIndex(l,index)
	If n <> Null Then n\Value = value:Else Return 0
End Function

;Remove and return a value at the given position (added by MusicianKool)
Function RemoveNodeAtIndex(l.LList,index)
	Local n.ListNode = ListNodeAtIndex(l,index),tval
	If n <> Null Then tval = n\Value:RemoveListNode(n):Return tval:Else Return 0
End Function

;Retrieve the first value from a list
Function ListFirst(l.LList)
	If l\head_\nx_ <> l\tail_ Then Return l\head_\nx_\Value
End Function

;Retrieve the last value from a list
Function ListLast(l.LList)
	If l\tail_\pv_ <> l\head_ Then Return l\tail_\pv_\Value
End Function

;Remove the first element from a list, and return its value
Function ListRemoveFirst(l.LList)
	Local val
	If l\head_\nx_ <> l\tail_
		val = l\head_\nx_\Value
		RemoveListNode l\head_\nx_
	EndIf
	Return val
End Function

;Remove the last element from a list, and return its value
Function ListRemoveLast(l.LList)
	Local val
	If l\tail_\pv_ <> l\head_
		val = l\tail_\pv_\Value
		RemoveListNode l\tail_\pv_
	EndIf
	Return val
End Function

;Insert a value into a list before the specified node, and return the new node
Function InsertBeforeNode.ListNode(Value, n.ListNode)
	Local bef.ListNode = New ListNode
	
	bef\pv_ = n\pv_
	bef\nx_ = n
	bef\Value = Value
	
	n\pv_ = bef
	bef\pv_\nx_ = bef
	
	Return bef
End Function

;Insert a value into a list after the specified node, and return then new node
Function InsertAfterNode.ListNode(Value, n.ListNode)
	Local aft.ListNode = New ListNode
	
	aft\nx_ = n\nx_
	aft\pv_ = n
	aft\Value = Value
	
	n\nx_ = aft
	aft\nx_\pv_ = aft
	
	Return aft
End Function

;Get an iterator object to use with a loop
;This function means that most programs won't have to think about deleting iterators manually
;(in general only a small, constant number will be created)
Function GetIterator.Iterator(l.LList)
	Local i.Iterator
	
	If l = Null Then RuntimeError "Cannot create Iterator for Null"
	
	For i = Each Iterator		;See if there's an available iterator at the moment
		If i\l_ = Null Then Exit
	Next
	
	If i = Null Then i = New Iterator	;If there wasn't, create one
	
	i\l_ = l
	i\cn_ = l\head_
	i\cni_ = -1
	i\Value = 0		;No especial reason why this has to be anything, but meh
	
	Return i
End Function

;Use as the argument to While to iterate over the members of a list
Function EachIn(i.Iterator)
	
	i\cn_ = i\cn_\nx_
	
	If i\cn_ <> i\l_\tail_		;Still items in the list
		i\Value = i\cn_\Value
		i\cni_ = i\cni_ + 1
		Return True
		
	Else
		i\l_ = Null		;Disconnect from the list, having reached the end
		i\cn_ = Null
		i\cni_ = -1
		Return False
		
	EndIf
End Function

;Remove from the containing list the element currently pointed to by an iterator
Function IteratorRemove(i.Iterator)
	If (i\cn_ <> i\l_\head_) And (i\cn_ <> i\l_\tail_)
		Local temp.ListNode = i\cn_
		
		i\cn_ = i\cn_\pv_
		i\cni_ = i\cni_ - 1
		i\Value = 0
		
		RemoveListNode temp
		
		Return True
	Else
		Return False
	EndIf
End Function

;Call this before breaking out of an EachIn loop, to disconnect the iterator from the list
Function IteratorBreak(i.Iterator)
	i\l_ = Null
	i\cn_ = Null
	i\cni_ = -1
	i\Value = 0
End Function


;~IDEal Editor Parameters:
;~F#5#A#10#18#2A#32#3E#47#4C#58#66#6F#78#8F#9B#A9#B7#BD#C5#CC
;~F#E3#E9#EF#F4#F9#103#10D#11B#12B#13F#152#163
;~C#Blitz3D

Comments

MusicianKool2011
This is perfect. The only thing i see as a problem is
;Search a list to retrieve the first node with the given value
Function ListFindNode.ListNode(l.LList, Value)
	Local n.ListNode = l\head_\nx_
	
	While n <> l\tail_
		If n\Value = Value Then Return n
	Wend
	
	Return Null
End Function

needs:
While n <> l\tail_
If n\Value = Value Then Return n
------>n = n\nx_
Wend


Yasha2011
Nice catch, thank you!

...yeah you can tell I didn't exactly test this thoroughly (*cough* at all *cough*).


MusicianKool2011
here is an addition, to treat the linked list like an array.
;Replace a value at index)
Function ReplaceValueAtIndex(l.LList,index,value)
	Local n.ListNode = ListNodeAtIndex(l,index)
	If n <> Null Then n\Value = value:Else Return 0
End Function

Function RemoveNodeAtIndex(l.LList,index)
	Local n.ListNode = ListNodeAtIndex(l,index),tval
	If n <> Null Then tval = n\Value:RemoveListNode(n):Return tval:Else Return 0
End Function



MusicianKool2011
Found another issue, or safety for the insert before and after.
;Insert a value into a list before the specified node, and return the new node
Function InsertBeforeNode.ListNode(Value, n.ListNode)
	Local l.LList = n\Parent
	If l\head_ = n Then Return ListAddFirst(l,Value$)
		
	Local bef.ListNode = New ListNode
	
	bef\pv_ = n\pv_
	bef\nx_ = n
	bef\Value = Value
	bef\Parent = n\Parent
	
	n\pv_ = bef
	bef\pv_\nx_ = bef
	
	Return bef
End Function

;Insert a value into a list after the specified node, and return then new node
Function InsertAfterNode.ListNode(Value, n.ListNode)
	Local l.LList = n\Parent
	If l\tail_ = n Then Return ListAddLast(l,Value)
	Local aft.ListNode = New ListNode
	
	aft\nx_ = n\nx_
	aft\pv_ = n
	aft\Value = Value
	aft\Parent = n\Parent
	
	n\nx_ = aft
	aft\nx_\pv_ = aft
	
	Return aft
End Function



Yasha2011
Thanks for the additions.

I should point out that the ListNode type as given doesn't have a "Parent" member any more (it did in the original forum thread) as I felt it was more or less unnecessary, and more importantly, meant SwapLists would cease to be O(1) and become O(n).

I'm not sure there are many uses for SwapLists, but I thought it was more useful than a parent field since I can't think of any sensible uses for that one at all.


MusicianKool2011
Found one more thing.

should be.



Yasha2011
Updated. Thanks for noticing that last one - that's pretty important!


Yasha2012
Couple of extension packages -

Higher-order list functions using function pointers ("List-Fptr.bb"):




Higher-order list functions using closures ("List-Lambda.bb"):




They add exactly the same features, just designed to work with different callback systems (native function pointers for the first, functor objects for the second):

-- Copy/Free methods (so that copying or freeing a list can cleanup its owned objects, or create new ones for the new list)

-- Sort
-- Map
-- ForEach
-- Left and right folds
-- Left and right unfolds (see SRFI-1 for the direct source & usage)
-- Positive, negative and partition filters


Both of these require MikhailV's FastPointer DLL (the latter only indirectly), which is why they aren't in the main include file: they can't be implemented in "pure" Blitz Basic.

You can use both in the same project, if you wish: the names are suffixed with an F or an L respectively so that there are no namespace clashes.


Code Archives Forum