Is dynamic custom array sorting possible?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Texfy

I stumbled on this problem during my current project and am looking for suggestions on how to achieve it in GDScript.

I want to sort objects in an array based on properties I can access through the objects get functions. I know I can write a custom sort function for a specific property, e.g. health, like this:

func custom_sort(a, b):
    return a.get_health() < b.get_health()

And I can use it on an array arr by calling arr.sort_custom(self, 'custom_sort').

In my project, I now need to dynamically sort objects by different properties, the get functions of which sometimes require additional parameters. Because there are many properties, what I would really want to have is a custom sort function that can take the get function and its parameters as parameters, something like this:

func custom_sort(a, b, property: String, parameters: Array=[]):
    return a.call(property, parameters) < b.call(property, parameters)

( I know call() would actually require the parameters as a comma-separated list, but since this isn’t the problem here, let’s just ignore this part)

However, it seems custom sort functions cannot take additional parameters, and there is no way to pass parameters to them anyway in the arr.sort_custom() call.

The only other approach short of manually defining a custom sort function for every property I want to sort by is somehow constructing an appropriate custom sort function in code and then passing it to arr.sort_custom(). Based on my understanding, GDScript doesn’t have first-class functions though, and while I could use func_ref() to reference and pass a function, I cannot construct one from code.

So is there any other way of achieving this behavior? Or is there any way to circumvent this problem entirely (without manually defining all custom sort functions, of course)?

:bust_in_silhouette: Reply From: Zylann

If I understdood correctly, that doesnt sound like “dynamic sorting”, but rather more complicated sorting. I guess it would be dynamic if the array needed to be re-sorted due to some properties changing over time.

There are a few ways you could do this.


One way is to realize that in this code:

func custom_sort(a, b):
	return a.get_health() < b.get_health()

a and b are objects. So what stops you from using a different class for each different object, but define a get_priority() or compare(other) function in all of them? This way the sorting function would not need to know about the complexity and each function can be implemented in a custom way per object.

func custom_sort(a, b):
	return a.get_priority() < b.get_priority()

Or this:

func custom_sort(a, b):
	return a.compare_to(b)

Another option would be like you said, pass some stuff to custom_sort. It is true you can’t pass it as arguments, but remember custom_sort is a method. That means it is part of an object. And that object can have member variables that are accessible during sorting.
So if you’re used to lambdas with captures, this is the same as a sorting lambda with 2 captured variables c and d:

class Sorter:
	var c
	var d
	func custom_sort(a, b):
		return (a + c) < (b + d)

var sorter = Sorter.new()
sorter.c = 42
sorter.d = 666
array.sort_custom(sorter, "cutom_sort")

Thanks for the reply! The second option is what I went with. I didn’t realize that custom sort can be a method of any object; so I implemented a Sorter class that takes the method to sort by and optional method parameters as parameters in its _init() function and then uses them in sort().

Texfy | 2022-03-15 18:32