Quickest way to sort list?

BlitzMax Forums/BlitzMax Programming/Quickest way to sort list?

Grey Alien(Posted 2006) [#1]
Hi, I've got a TList of types and each type has x and y coords. I want to sort the list of types in descending y coordinate order (with greatest value first).

I'm actually converting some old BlitzPlus code that used an array and did a bubble sort on it, which was fine, but bubble sorts aren't that fast. The list can be between 100-200 items so iterating through it too many times isn't cool.

I haven't yet investigated the built-in sorting capabilities of TList including the fact that you can supply your own compare function or something. But I had read somewhere that it was a bit bugged, is this still true?

Anyway, I was thinking about using this method: Basically I make a new TList and fill that up. A single loop through the original list is required, but for each item in the original list I need to loop through the destination list to find the correct place to insert it. Obviously at the start the destination list won't be very big, but by the end it will contain many items. So this method sounds a tad slow. But is it faster than a bubble sort I wonder?

I could try a binary search + sort but that only works on already sorted lists. Other weird and wonder method are listed on wikipedia. What ones have you found easily to implement with good results?

Oh yes, I'm aware that a TList can be converted into and array and back again, maybe I should do that first?

Thanks in advance.


fredborg(Posted 2006) [#2]
Hi,

Yes, convert it to an array, sort the array, and convert it back. It's reasonably fast, and saves you the trouble of making your own sorting function.


Grey Alien(Posted 2006) [#3]
OK thanks. Well I still need a sorting function for the array of types because I'm sorting by the types' y coordinates, not the actual types themselves...


tonyg(Posted 2006) [#4]
Which of those options have you tried?
What didn't you like about them when you actually *tried* them rather than thought about doing them?
Is is possible to sort the list first and then insert in the correct place? How often would it need sorting? Can you spread the sort over seperate frames?
The quickest way to sort a list might be some hideously complicated, over-the-top, not necessary for what you're trying to do type of process.
P.S. I'm not aware of having a problem with the Compare method although SSwift has posted a few times that it messes up findlink and stored links. Maybe, for whatever it is you're doing, that won't be a problem.


Grey Alien(Posted 2006) [#5]
Well in my last match-3 game the bubble sorted array worked and was fast but if called a few times a frame due to lots of changes, it could cause a spike in CPU use and result in slowdown on some machines.

Also I'm asking advice before I embark on the code in case anyone has tried this before (which they will have) and has some useful info. I think research is important.

The sort can't be spread over several frames, it may need to occur more than once per frame as several different routines alter the y coords of the types and therefore the list needs to be resorted. Also some functions depend on the y coords being sorted before they are called. Why? Well the types drop down with gravity and the lowest ones must be moved first - this happens each frame but other actions during the frame may alter their y coords. The list is populated at the level start and can be sorted then with a slow function. After that, when types are deleted or added (sometimes from the top of the screen, other times in the middle), the items need to be added in the correct place, so bunging them on the end of the list and doing an overall sort seemed attractive if slower...kinda fire and forget. That's why I was looking for list sorting code.


tonyg(Posted 2006) [#6]
If you can start with a sorted list then you won't need to sort it all again just have InsertAfterlink using an index on 'Y'. Somebody will mention TMAP but that's beyond me at the momentm although there's a good example somewhere.
<edit> p.s. with 100-200 objects on a list you might even be able to use the list commands rather than the link methods.
<edit> P.P.S I think the Compare issue was fixed by the extra comparefunc parm.


Grey Alien(Posted 2006) [#7]
Yes I can start with a sorted list (making the code now) but how do I make an "index on Y"? Do you mean just loop through checking Y and insert at the correct point? I guess so. Deleting is fine as the list will handle that automatically, but what about when the Y coordinate of something changes, I guess I'll have to just loop to find the new position and move the link (can you move a link or is it just a case of backup/delete/insert?).

Hmm, OK I was going for the brute force approach of sort each time but after thinking a bit more (thanks TonyG) I realise I can be a bit cleverer and just insert or recalc the list position of anything that has moved ...


Grey Alien(Posted 2006) [#8]
TonyG: The docs say

[code]Method Sort( ascending=True,compareFunc( o1:Object,o2:Object )=CompareObjects )[/quote] What's this extra comparefunc param? [edit] It's OK I found this:

http://www.blitzbasic.com/Community/posts.php?topic=59675#665275


Grey Alien(Posted 2006) [#9]
Hmm, OK I've implement a full sort using CompareFunc and it's fine and fast too.

I made this insert method but I'm not convinced it's that fast because doesn't FindLink have to loop through the whole list looking for the matching object? Is this a dumb way to do it? Should I be looping through the Links directly instead of the list, and are the links *always* in the same order as the the list (maybe that's a dumb question)? Heres some code:

Type TGemList Extends TList
	Method InsertGem(ins:TGem)		
		'Loop through the already sorted (largest Y first) list checking the Y coord
		'and insert the passed in Gem in the correct place.
		Local added=0
		For Local g:TGem = EachIn Self
			If ins.Y > g.Y Then
				InsertBeforeLink(ins, FindLink(ins))
				Added=1
				Exit 'done
			EndIf
		Next
		'If it wasn't inserted, add on the end
		If Not added Then AddLast(ins)		
	End Method
End Type



tonyg(Posted 2006) [#10]
It would probably be quicker to do it by link or via TMAP/Hash 'map'(?) but looping and inserting into 100-200 will be negligable.
Try timing it and see what happens.
<edit> You might now want to override the Compare method to do your findlink()


Grey Alien(Posted 2006) [#11]
I guess you are right, (it is effectively looping twice though, once to compare, and again to find the correct link) but I could be doing it up to say 20 times a frame. It's really the idea that if you have a game, this is just one part of the logic and where possible I try to get it all optimised well so that overall it runs well to keep a high FPS.


tonyg(Posted 2006) [#12]
Why loop twice? Use the Compare override and then Insert


Grey Alien(Posted 2006) [#13]
I'll think about that, but I don't fully understand how to implement that...Anyway, surely compare has to loop as well?


tonyg(Posted 2006) [#14]
Yep, compare and insert in the same loop if you're list is already sorted.


Grey Alien(Posted 2006) [#15]
My compare funciton looks like this:

	Function CompareY%(o1:Object, o2:Object)
		If TGem(o1).Y<TGem(o2).Y Then Return 1
		Return 0
	End Function		

Then in TGemList I have this method:
	Method SortAll()
		Sort(True, TGem.CompareY)
	EndMethod	

I'm not sure how I'm supposed to use that function in the InsertGem method I posted...?


tonyg(Posted 2006) [#16]
You should be using your comparefunc to override the sortlist function and compare to override the findlink function. Findlink will return the link which you use in InsertAfterLink or InsertBeforeLink.
Alternatively, do it by hand. Check your.y against listobject.y, get the listfindlink using the matching object then InsertAfter/BeforeLink.


Grey Alien(Posted 2006) [#17]
regretably you've lost me. I can't be to hot on TLists and TLinks oh well...I do understand overriding in derived types though, luckily.


tonyg(Posted 2006) [#18]
OK, last one before Germany vs Argentina.
Your list has been sorted using SortList with your CompareFunc method.... OK?
That leaves the FindLink method free to use your overridden Compare method. Your custom compare method returns the link of the object 'matching' your 'to-be-inserted' object.
This is used to InsertAfterLink (or InsertBeforeLink if required) the new object onto the list.


Grey Alien(Posted 2006) [#19]
WAH! You just better go and watch the footbal. Thanks anyway...


Grey Alien(Posted 2006) [#20]
In the end I changed the Insert method to this:

	Method InsertGem(ins:TGem)		
		'Loop through the already sorted (largest Y first) list checking the Y coord
		'and insert the passed in Gem in the correct place.
		Local added=0
		Local L:TLink = FirstLink()
		While True
			Local g:TGem = TGEm(L.Value())
			If ins.Y > g.Y Then
				InsertBeforeLink(ins, L)
				Added=1
				Exit 'done
			EndIf
			L = L.NextLink()
			'Is the end of the list reached?
			If L = Null Then Exit
		Wend
		'If it wasn't inserted, add on the end
		If Not added Then AddLast(ins)		
	End Method


It's just using links so should be faster.


computercoder(Posted 2006) [#21]
tonyg:

I totally understand what you are saying! It took me a few hours to absorb what was going on here, but now that I have working code, it makes perfect sense!

Thanks!


doswelk(Posted 2008) [#22]
Ignore I'd forgot too check to see if I was adding the first entry....


Czar Flavius(Posted 2008) [#23]
You got me excited. I could actually have slightly helped on sorting functions.


ImaginaryHuman(Posted 2008) [#24]
"TGemList Extends TList
Method InsertGem(ins:TGem)"

Hey, are you letting on that your new game has gems in it ;-D