How to get random object with given property from array of objects?

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

I have an array of objects. Each object can have properties. I need a take a random element of filtered array by some condition. For i.e. random element with top priority. The problem that I don’t know what priority is top, so I can’t just go through array and make new array with that priority. Or condition can be more complicated (for i.e. random object with health more 50 and closer to player than 100)

:bust_in_silhouette: Reply From: Robotex

Here is my variant of solution of this problem:

extends Node

var arr = ["Tst1", "Tst2", "Tst3", "Test4", "Test5", "t1", "t2", "tt1", "tt2"]
var dict_arr = [{"value": 50}, {"value": 100, "name": "test1"}, {"value": 100, "name": "test2"}, {"value": 20}, {"value": 110, "name": "test3"}, {"value": 110, "name": "test4"}, {"value": 110, "name": "test5"}]

func _ready():
	randomize()
	print(random_element(arr))
	print(random_element_with_biggest_property(dict_arr, "value"))
	print(random_element_with_condition(arr, funcref(self, "lengthBigger2")))
	print(random_element_with_condition(arr, funcref(self, "lengthBigger3")))
	print(random_element_with_condition(arr, funcref(self, "lengthBigger4")))
	
func random_element(array):
	return array[randi() % array.size()]

func random_element_with_biggest_property(array, property : String):
	if array.empty():
		return null
		
	var accepted = []
	var biggestValue = array[0].get(property) if property in array[0] else null
	
	for element in array:
		if property in element:
			var value = element.get(property)
			
			if value > biggestValue:
				accepted.clear()
				biggestValue = value
				accepted.append(element)
			elif value == biggestValue:
				accepted.append(element)
	
	if accepted.empty():
		return null
	else:
		return random_element(accepted)

func random_element_with_condition(array, conditionalFunc : FuncRef):
	if array.empty():
		return null
		
	var accepted = []
	
	for element in array:
		if conditionalFunc.is_valid() and conditionalFunc.call_func(element):
			accepted.append(element)
	
	if accepted.empty():
		return null
	else:
		return random_element(accepted)

func lengthBigger2(element):
	return element.length() > 2
	
func lengthBigger3(element):
	return element.length() > 3
	
func lengthBigger4(element):
	return element.length() > 4

Can you review it? What problems does it have? What is about performance?

Is it possible to find more elegant solution?

I’m a little confused, in your question you call them objects with properties, but here they seem to be string literals with loosely associated values in a dictionary (the string literals in your dictionary do not exactly match your capitalization in your array, and the first item in your dictionary has no “name” key).
When I read your question I expected custom objects with member variables as properties, which is probably how I would do it. Do these objects have representation in your game beyond “test” (are they monsters, players, items, etc)? The solution you’ve got now adds a convoluted (IMO) layer of abstraction that divorces the original data from how you want to manipulate it. In other words, those dictionaries have to be populated somehow, and you may introduce bugs in the transcription process that can be difficult to diagnose. If you instead used an array of references to objects in memory, potentially with a custom class (declared at the top of the object’s script with the class_name keyword in GDScript), you could construct a for loop to iterate over the collection, creating another collection of references based on some condition, and then randomly choosing out of that second collection.
A hypothetical example using custom objects with class_name keyword and for loop member variable checking (not a complete implementation of what you want, but hopefully will get you there):
Class1.gd:

extends Object

class_name Class1

var foo = 20
var bar = "spam"

Class2.gd:

extends Object

class_name Class2

var foo = 20
var baz = "runcible"

debug_scene.gd:

extends Node2D

var collection = []

func _ready():

	for i in 10:
		var object1 = Class1.new()
		var object2 = Class2.new()
		collection.push_back(object1)
		collection.push_back(object2)
	
	for item in collection:
		print("item number", collection.find(item))
		if "foo" in item:
			print("item has member variable foo")
		if "bar" in item:
			print("item has member variable bar")
		if "baz" in item:
			print("item has member variable baz")

output:

item number0
item has member variable foo
item has member variable bar
item number1
item has member variable foo
item has member variable baz
item number2
item has member variable foo
item has member variable bar
item number3
item has member variable foo
item has member variable baz
...

DDoop | 2020-08-06 16:48

in your question you call them objects with properties, but here they
seem to be string literals with loosely associated values in a
dictionary

it will be scene nodes and gdscript objects in the real game. I guess, that interface the same as with the dictionaries

Robotex | 2020-08-06 17:04