TList question - Moving through list

BlitzMax Forums/BlitzMax Beginners Area/TList question - Moving through list

Hi,
Always get frustrated with TList's. There is a command

MyTList.First

This moves the point to the first entry. There is also the command

MyTList.Last

But if I want to move forward 1 entry though the list, how do I do it without using a for... each loop. In BlitzPlus, sure there was a command like MyTList.Next but cant seem to find the same under BlitzMAX

Sin

http://www.blitzbasic.com/Community/posts.php?topic=50285

Thanks, but to be honest that seems quite extreme to do a simple task. Cant believe they did not add that command to BlitzMax.

All I want to do is change a value at a particular entry (String TList). The MyTList.ValueAtIndex(Entry) only displays it, but I want to actually change that value at that point.

I cant use for...each loop (wont go into why). Anyone able to whip up a quick example?

Sin

Local txt:Object[] = ListToArray(list)
txt[index] = "hello"
list = ListFromArray(txt)


Cant believe they did not add that command to BlitzMax.

It isn't possible to do it exactly the same way. Lists do much more than they did in B3D/B+.

The MyTList.ValueAtIndex(Entry) only displays it, but I want to actually change that value and that point.

I have no idea what you mean. Either you have the type instance you want or you don't. There's no such thing as read-only types.

Type Thing
	Field X:Int
End Type

Global List:TList


Global AThing:Thing=New Thing
AThing.X=5
List=New TList
List.AddLast(AThing)

Global BThing:Thing=Thing(List.ValueAtIndex(0))
BThing.X=10

Print AThing.X


	Method Before:Object()
	
		Local link:TLink=List.FindLink(Self).PrevLink:TLink()
		If link<>Null
			Return link.Value:Object()
		Else
			Return Null
		EndIf
	
	End Method

	Method After:Object()
	
		Local link:TLink=List.FindLink(Self).NextLink:TLink()
		If link<>Null
			Return link.Value:Object()
		Else
			Return Null
		EndIf
	
	End Method


Bare with me, my head hurts with this stuff. :-D What I ment is that you cant do:

MyTList.ValueAtIndex(Entry) = "Hello"


Thanks, but what I'm having trouble with is that fact I have a Tlist of Strings. There are no Types involved

Global MyTList:TList = CreateList()

For x:Int = 1 to 10
   ListAddLast MyTList,String(x)
Next


OK, now I want to change the value at position 4 to say "bob" without using a for...each loop.

I have not defined any types (as the added entries are just strings) and thats what my head cant get round.

I tried the ToArray and FromArray command but it wont update my list when using the FromArray command???

Sin

OK, I've never used the TLink command before. If I didnt want to create a method and access it directly, how would I do it?

This does not work, as I dont know what to pass as the FindLink() method.

Global MyTList:TList = CreateList()

For x:Int = 1 To 10
   ListAddLast MyTList,String(x)
Next



For Local I:String = EachIn MyTList
	Print(I)
Next	


Local Test1:String

Test1 = String(MyTList.ValueAtIndex(4))
Print "Test1 = "+Test1


Local link:TLink=MyTList.FindLink(MyTList.ValueAtIndex(4))
Test2 = String(link.value)
Print "Test2 = "+Test2

WaitKey()


http://www.blitzbasic.com/codearcs/codearcs.php?code=1584

Looking at the TList type I don't know why you don't just look at the pred/succ fields which point directly to the previous and next objects in the list ie mylist._succ

I can't believe ListNext isn't a command. So many people keep having this same problem. It really should be built in even if it has to search the list. Look at ListRemove, its already known to be slow because it searches the whole list. Yet we use it anyways because its simple.

**AngelDaniel**
I dont understand. How do I use this MyList._succ you speak of? Comes up with errors in the example above.

Print MyTList._succ 

Can you show me what you mean?

*** Curtastic ***
I agree 100%. I got used to BlitzPlus and then they changed it in BlitzMax. Just having a few issues adjusting.

Has anyone got any tutorials on TLists and more importantly TLinks? I'm sorry, but the BlitzMax docs simply dont cut it for explanations the use of commands.


Sin

The TList object is the `header` of the linked list, and it contains a pointer to the first link in the list. Each link is, if I remember right, a `TLink` object, containing a pointer to the previous TLink and a pointer to the next TLink and a pointer to the object that you stored in the link. So you need to work from the link, not the list itself. Get the link of the object that you are interested in, which returns a TLink object, and then go myTLink._succ to get the next link or myTLink._pred to get the previous link. Note that these are internal variables so BRL can potentially change them in future. All you're doing is directly accessing the previous or next link from within a given link, which is what you want. I thought there was a method that does this?

Here's what the TLink object looks like (from the linkedlist.mod module):

Type TLink

	Field _value:Object
	Field _succ:TLink,_pred:TLink
	
	Rem
	bbdoc: Returns the Object associated with this Link.
	End Rem
	Method Value:Object()
		Return _value
	End Method

	Rem
	bbdoc: Returns the next link in the List.
	End Rem
	Method NextLink:TLink()
		If _succ._value<>_succ Return _succ
	End Method

	Rem
	bbdoc: Returns the previous link in the List.
	End Rem
	Method PrevLink:TLink()
		If _pred._value<>_pred Return _pred
	End Method

	Rem
	bbdoc: Removes the link from the List.
	End Rem
	Method Remove()
		_value=Null
		_succ._pred=_pred
		_pred._succ=_succ
	End Method

End Type

So either you can find the link using myLink:TLink=myTList.FindLink(), and then do nextLink=myLink._succ (or myLink._pred for previous), or you can use the method myTLink.NextLink() and myTLink.PrevLink()

Also look at this code from the TList object: It get the _head field from the TList object, which is the pointer to the first TLink in the list, and then goes through all of them to find the link that you want.
	Method FindLink:TLink( value:Object )
		Local link:TLink=_head._succ
		While link<>_head
			If link._value.Compare( value )=0 Return link
			link=link._succ
		Wend
	End Method

I prefer to either write my own more efficient linked list or at least look at _pred and _succ rather than go via a timewasting `get` method.

I wrote this example which uses TLink to move up/down through a list. Guidance is in the code:

SuperStrict

Framework BRL.GLMax2D
Import BRL.Random

' Initialise a TList called mylist
' This is used to hold a list of strings
Global mylist:TList=New TList

' add some items to mylist
mylist.AddLast "Apple"
mylist.AddLast "Banana"
mylist.AddLast "Pear"
mylist.AddLast "Blackberry"
mylist.AddLast "Peach"
mylist.AddLast "Mango"

' Initialise a TLink called link
' Set it to point to the first item in mylist
Global link:TLink=mylist.FirstLink()

' store the string from the first item into selected$
Global selected$=String(link.Value())

' open a small graphic window
AppTitle = "Lists - Next/Previous example"
Graphics 400,300,0
SetClsColor $aa,$aa,$aa


Repeat
	
	' if user presses ESCAPE break out of the loop
	If KeyHit(KEY_ESCAPE) Exit
	
	' if user presses DOWN ARROW move to next item in list
	If KeyHit(KEY_DOWN) 
		link=link.NextLink()
		If link=Null link=mylist.FirstLink()
		selected$=String(link.Value())
	EndIf
	
	' if user presses UP ARROW move to previous item in list
	If KeyHit(KEY_UP)
		link=link.PrevLink()
		If link=Null link=mylist.LastLink()
		selected$=String(link.Value())
	EndIf
	
	' If DEL pressed remove selected item
	' Now point to the first item in the remaining list
	' If the list is empty exit from the loop
	If KeyHit(KEY_DELETE)
		mylist.Remove selected$
		link=mylist.FirstLink()
		If link=Null Exit
		selected$=String(link.Value())
	EndIf

	' insert an entry AFTER the currently selected one
	If KeyHit(KEY_A)
		mylist.InsertAfterLink("Lemon"+Rand(100),link)
	EndIf
	
	' insert an entry BEFORE the currently selected one
	If KeyHit(KEY_B)
		mylist.InsertBeforeLink("Lemon"+Rand(100),link)
	EndIf

	' if user presses RETURN key show item selected
	If KeyHit(KEY_RETURN)
		Notify "Your choice is "+selected$
	EndIf
	
	' show list and highlight the 'selected' item
	Cls
	SetColor $88,$44,$00
	DrawText "UP/DOWN = select item",10,10
	DrawText "RETURN  = confirm selection",10,24
	DrawText "DEL     = Remove selected item",10,38
	DrawText "A       = Insert 'Lemon' AFTER selected item",10,52
	DrawText "B       = Insert 'Lemon' BEFORE selected item",10,66
	Local y%=100
	For Local l$=EachIn mylist
		SetColor $22,$22,$22
		If l$=selected$	SetColor $ff,$ff,$ff
		DrawText l$,40,y
		y:+16
	Next

	Flip

Forever

End




And a simplified / clearer example to follow:
Framework BRL.StandardIO
Import BRL.LinkedList


Global list:TList=New TList
Global link:TLink


list.addlast "House"
list.addlast "Garden"
list.addlast "Fence"
list.addlast "Tree"

link=list.FirstLink() ' first
Print String(link.Value())

link=link.NextLink() ' next
Print String(link.Value())

link=link.PrevLink() ' previous
Print String(link.Value())

link=list.LastLink() ' last
Print String(link.Value())

' finding an entry
link=list.FindLink("Fence")
Print String(link.Value())


Thanks! That is much clearer now.

TLink is pretty much the same as link lists in C++/Pascal (showing my age). I think I can work round my problem with those examples :D

Cheers

Sin

Jim Brown:
Hi, 1 quick question. Your example code does everything except change a current an existing value to something else.

I'm assuming this is not possible, so how would I do it?

link.value() = "Bob"


Sin

Sin, this should work

link._value = "Bob"


Note that link._value is of type object, so you can't assign it a simple number or float or anything. Strings are objects in Blitzmax, hence why the above will work.

As SculptureSoul says, use link._value = "blah"

Revised both of my examples from above:

SuperStrict

Framework BRL.GLMax2D
Import BRL.Random

' Initialise a TList called mylist
' This is used to hold a list of strings
Global mylist:TList=New TList

' add some items to mylist
mylist.AddLast "Apple"
mylist.AddLast "Banana"
mylist.AddLast "Pear"
mylist.AddLast "Blackberry"
mylist.AddLast "Peach"
mylist.AddLast "Mango"

' Initialise a TLink called link
' Set it to point to the first item in mylist
Global link:TLink=mylist.FirstLink()

' store the string from the first item into selected$
Global selected$=String(link.Value())

' open a small graphic window
AppTitle = "Lists - Next/Previous example"
Graphics 400,300,0
SetClsColor $aa,$aa,$aa


Repeat
	
	' if user presses ESCAPE break out of the loop
	If KeyHit(KEY_ESCAPE) Exit
	
	' if user presses DOWN ARROW move to next item in list
	If KeyHit(KEY_DOWN) 
		link=link.NextLink()
		If link=Null link=mylist.FirstLink()
		selected$=String(link.Value())
	EndIf
	
	' if user presses UP ARROW move to previous item in list
	If KeyHit(KEY_UP)
		link=link.PrevLink()
		If link=Null link=mylist.LastLink()
		selected$=String(link.Value())
	EndIf
	
	' If DEL pressed remove selected item
	' Now point to the first item in the remaining list
	' If the list is empty exit from the loop
	If KeyHit(KEY_DELETE)
		mylist.Remove selected$
		link=mylist.FirstLink()
		If link=Null Exit
		selected$=String(link.Value())
	EndIf

	' insert an entry AFTER the currently selected one
	If KeyHit(KEY_A)
		mylist.InsertAfterLink("Lemon_"+Rand(1000,9000),link)
	EndIf
	
	' insert an entry BEFORE the currently selected one
	If KeyHit(KEY_B)
		mylist.InsertBeforeLink("Lemon_"+Rand(1000,9000),link)
	EndIf

	' if user presses RETURN key show item selected
	If KeyHit(KEY_RETURN)
		Notify "Your choice is "+selected$
	EndIf
	
	' change current selection to 'cucumber'
	If KeyHit(KEY_C)
		link=mylist.FindLink(selected$)
		selected$="Cucumber_"+String(Rand(1000,9000))
		link._value=selected$
	EndIf
	
	' show list and highlight the 'selected' item
	Cls
	SetColor $88,$44,$00
	DrawText "UP/DOWN = select item",10,10
	DrawText "RETURN  = confirm selection",10,24
	DrawText "DEL     = Remove selected item",10,38
	DrawText "A       = Insert 'Lemon' AFTER selected item",10,52
	DrawText "B       = Insert 'Lemon' BEFORE selected item",10,66
	DrawText "C       = Change selected item to 'Cucumber'",10,80
	Local y%=100
	For Local l$=EachIn mylist
		SetColor $22,$22,$22
		If l$=selected$	SetColor $ff,$ff,$ff
		DrawText l$,40,y
		y:+16
	Next

	Flip

Forever

End



Framework BRL.StandardIO
Import BRL.LinkedList


Global list:TList=New TList
Global link:TLink

list.AddLast "House"
list.AddLast "Garden"
list.AddLast "Shed"
list.AddLast "Fence"
list.AddLast "Tree"

ShowList "Current list:"
Print "~n"

link=list.FirstLink() ' first
Print "FIRST              = " + String(link.Value())

link=link.NextLink() ' next
Print "NEXT               = " +  String(link.Value())

link=list.LastLink() ' last
Print "LAST               = " + String(link.Value())

link=link.PrevLink() ' previous
Print "PREVIOUS           = " + String(link.Value())

' finding an entry
link=list.FindLink("Shed")
Print "FIND 'Shed'        = " + String(link.Value())

' change entries value (text)
link._value="Flower"
Print "CHANGE to 'Flower' = " + String(link.Value())

' remove entry
list.Remove "Fence"
Print "REMOVE 'Fence'    = (n/a)"

ShowList "Final list:"
End

Function ShowList(desc$)
	Local c%
	Print "~n"+desc$
	Print "_____________________________"
	For Local n$=EachIn list
		c:+1
		Print "Entry #"+String(c)+") ~q"+n$+"~q"
	Next
End Function


I'd do it this way. Personal preference I suppose, I just find it simpler. This isn't checking for errors but all you'd need to do is check for null returns before trying to change Type contents:
Global stringList:TList = New TList
Local s:tString

'put some random chars in list
For N = 1 To 10
	s = New tString
	s.text = Chr$(Rand(65,97))
	stringList.addLast s
Next

'change value of item 4
s = tString(stringList.valueAtIndex(4))
s.text = "Replaced this item!"

'find next link and change value
getNext(s).text = "Replaced the next one too!"


'output
For s = EachIn stringList
	DebugLog s.text
Next

'function to get next object in list
Function getNext:tString(nLink:tString)
	nLink = tString(stringList.FindLink(nLink).NextLink().Value())
	Return nLink
End Function

'declaration
Type tString
	Field text:String
End Type


The use of next,prev etc is fine for both string and type objects but how does FindLink work with type objects?
Here's Jim's code changed to use a TTEST
Framework BRL.StandardIO
Import BRL.LinkedList


Global list:TList=New TList
Global link:TLink
Local name_array:String[] = ["House" , "Garden" , "Shed" , "Fence" , "Tree"]
Type ttest
	Field name:String
End Type
For Local x:Int = 0 To Len(name_array) - 1
	Local temp:ttest = New ttest
	temp.name = name_array[x]
	ListAddLast list,temp
Next
Print "_____________________________"

link=list.FirstLink() ' first
Print "FIRST              = " + ttest(link.Value()).name

link=link.NextLink() ' next
Print "NEXT               = " +  ttest(link.Value()).name

link=list.LastLink() ' last
Print "LAST               = " + ttest(link.Value()).name

link=link.PrevLink() ' previous
Print "PREVIOUS           = " + ttest(link.Value() ).name

'********** This bit ****************
'' finding an entry
'link=list.FindLink("Shed")
'Print "FIND 'Shed'        = " + String(link.Value())
'
'' change entries value (text)
'link._value="Flower"
'Print "CHANGE to 'Flower' = " + String(link.Value())
'

As we no longer have 'known' or unique link names aren't we stuck to looping through the list checking each mytest.name until we find 'Shed' again.
Is this where you'd use a tmap or store a link in a type field (although won't we need to find the instance before we can use it?)?

Tony, that code won't work as FindLink is comparing the object provided as an argument against the object stored in each link. Since you don't have "Shed" stored anywhere, but instead have a TTEST in there, it won't find anything.

If you want to associate a string name (or key) to a value, you're better off using TMap's or my THashtable (http://www.blitzmax.com/codearcs/codearcs.php?code=1907)

For instance, with the THashtable, you could go
global Hash:THashtable = THashtable.Constructor(50) 'make a 50 element hash tale

global test:ttest = new ttest
test.name = "Test name"

Hash.InsertEntry( test.name, test )
'this stores the object test with the key "Test name". Of course, any string or numeric key could be used.



And then, when you want to retrieve an entry, all you need to know is it's key value

global ttest_return:ttest

'the basic call to get an object back is Hash.GetEntry( "key value here"), 
'but we must cast the result back since the hash stores & returns objects, so the final call ends up being

ttest_return = ttest(Hash.GetEntry( "Test name" ))


Note that key names are case sensitive.

Thanks SculptureOfSoul but isn't that what I said ;-)?

Well, technically you asked it, but yeah, I suppose so. ;)

Thanks all. Great info.

Sin