Răsfoiți Sursa

Merge pull request #95449 from SlashScreen/array_functions

Add callable support for `find` and `rfind` `Array` methods
Rémi Verschelde 11 luni în urmă
părinte
comite
6bf8a3e3f8

+ 64 - 1
core/variant/array.cpp

@@ -369,6 +369,34 @@ int Array::find(const Variant &p_value, int p_from) const {
 	return ret;
 }
 
+int Array::find_custom(const Callable &p_callable, int p_from) const {
+	int ret = -1;
+
+	if (p_from < 0 || size() == 0) {
+		return ret;
+	}
+
+	const Variant *argptrs[1];
+
+	for (int i = p_from; i < size(); i++) {
+		const Variant &val = _p->array[i];
+		argptrs[0] = &val;
+		Variant res;
+		Callable::CallError ce;
+		p_callable.callp(argptrs, 1, res, ce);
+		if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
+			ERR_FAIL_V_MSG(ret, "Error calling method from 'find_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+		}
+
+		ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, ret, "Error on method from 'find_custom': Return type of callable must be boolean.");
+		if (res.operator bool()) {
+			return i;
+		}
+	}
+
+	return ret;
+}
+
 int Array::rfind(const Variant &p_value, int p_from) const {
 	if (_p->array.size() == 0) {
 		return -1;
@@ -394,6 +422,41 @@ int Array::rfind(const Variant &p_value, int p_from) const {
 	return -1;
 }
 
+int Array::rfind_custom(const Callable &p_callable, int p_from) const {
+	if (_p->array.size() == 0) {
+		return -1;
+	}
+
+	if (p_from < 0) {
+		// Relative offset from the end.
+		p_from = _p->array.size() + p_from;
+	}
+	if (p_from < 0 || p_from >= _p->array.size()) {
+		// Limit to array boundaries.
+		p_from = _p->array.size() - 1;
+	}
+
+	const Variant *argptrs[1];
+
+	for (int i = p_from; i >= 0; i--) {
+		const Variant &val = _p->array[i];
+		argptrs[0] = &val;
+		Variant res;
+		Callable::CallError ce;
+		p_callable.callp(argptrs, 1, res, ce);
+		if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
+			ERR_FAIL_V_MSG(-1, "Error calling method from 'rfind_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+		}
+
+		ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, -1, "Error on method from 'rfind_custom': Return type of callable must be boolean.");
+		if (res.operator bool()) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
 int Array::count(const Variant &p_value) const {
 	Variant value = p_value;
 	ERR_FAIL_COND_V(!_p->typed.validate(value, "count"), 0);
@@ -761,7 +824,7 @@ Variant Array::max() const {
 				return Variant(); //not a valid comparison
 			}
 			if (bool(ret)) {
-				//is less
+				//is greater
 				maxval = test;
 			}
 		}

+ 2 - 0
core/variant/array.h

@@ -152,7 +152,9 @@ public:
 	void reverse();
 
 	int find(const Variant &p_value, int p_from = 0) const;
+	int find_custom(const Callable &p_callable, int p_from = 0) const;
 	int rfind(const Variant &p_value, int p_from = -1) const;
+	int rfind_custom(const Callable &p_callable, int p_from = -1) const;
 	int count(const Variant &p_value) const;
 	bool has(const Variant &p_value) const;
 

+ 2 - 0
core/variant/variant_call.cpp

@@ -2305,7 +2305,9 @@ static void _register_variant_builtin_methods_array() {
 	bind_method(Array, back, sarray(), varray());
 	bind_method(Array, pick_random, sarray(), varray());
 	bind_method(Array, find, sarray("what", "from"), varray(0));
+	bind_method(Array, find_custom, sarray("method", "from"), varray(0));
 	bind_method(Array, rfind, sarray("what", "from"), varray(-1));
+	bind_method(Array, rfind_custom, sarray("method", "from"), varray(-1));
 	bind_method(Array, count, sarray("value"), varray());
 	bind_method(Array, has, sarray("value"), varray());
 	bind_method(Array, pop_back, sarray(), varray());

+ 39 - 0
doc/classes/Array.xml

@@ -324,6 +324,7 @@
 			<param index="0" name="value" type="Variant" />
 			<description>
 				Returns the number of times an element is in the array.
+				To count how many elements in an array satisfy a condition, see [method reduce].
 			</description>
 		</method>
 		<method name="duplicate" qualifiers="const">
@@ -395,6 +396,25 @@
 				[b]Note:[/b] For performance reasons, the search is affected by [param what]'s [enum Variant.Type]. For example, [code]7[/code] ([int]) and [code]7.0[/code] ([float]) are not considered equal for this method.
 			</description>
 		</method>
+		<method name="find_custom" qualifiers="const">
+			<return type="int" />
+			<param index="0" name="method" type="Callable" />
+			<param index="1" name="from" type="int" default="0" />
+			<description>
+				Returns the index of the [b]first[/b] element in the array that causes [param method] to return [code]true[/code], or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the end of the array.
+				[param method] is a callable that takes an element of the array, and returns a [bool].
+				[b]Note:[/b] If you just want to know whether the array contains [i]anything[/i] that satisfies [param method], use [method any].
+				[codeblocks]
+				[gdscript]
+				func is_even(number):
+				    return number % 2 == 0
+
+				func _ready():
+				    print([1, 3, 4, 7].find_custom(is_even.bind())) # prints 2
+				[/gdscript]
+				[/codeblocks]
+			</description>
+		</method>
 		<method name="front" qualifiers="const">
 			<return type="Variant" />
 			<description>
@@ -618,6 +638,17 @@
 				func is_length_greater(a, b):
 				    return a.length() &gt; b.length()
 				[/codeblock]
+				This method can also be used to count how many elements in an array satisfy a certain condition, similar to [method count]:
+				[codeblock]
+				func is_even(number):
+				    return number % 2 == 0
+
+				func _ready():
+				    var arr = [1, 2, 3, 4, 5]
+				    # Increment count if it's even, else leaves count the same.
+				    var even_count = arr.reduce(func(count, next): return count + 1 if is_even(next) else count, 0)
+				    print(even_count) # Prints 2
+				[/codeblock]
 				See also [method map], [method filter], [method any] and [method all].
 			</description>
 		</method>
@@ -654,6 +685,14 @@
 				Returns the index of the [b]last[/b] occurrence of [param what] in this array, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the beginning of the array. This method is the reverse of [method find].
 			</description>
 		</method>
+		<method name="rfind_custom" qualifiers="const">
+			<return type="int" />
+			<param index="0" name="method" type="Callable" />
+			<param index="1" name="from" type="int" default="-1" />
+			<description>
+				Returns the index of the [b]last[/b] element of the array that causes [param method] to return [code]true[/code], or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the beginning of the array. This method is the reverse of [method find_custom].
+			</description>
+		</method>
 		<method name="shuffle">
 			<return type="void" />
 			<description>

+ 18 - 0
tests/core/variant/test_array.h

@@ -634,6 +634,24 @@ TEST_CASE("[Array] Typed copying") {
 	a6.clear();
 }
 
+static bool _find_custom_callable(const Variant &p_val) {
+	return (int)p_val % 2 == 0;
+}
+
+TEST_CASE("[Array] Test find_custom") {
+	Array a1 = build_array(1, 3, 4, 5, 8, 9);
+	// Find first even number.
+	int index = a1.find_custom(callable_mp_static(_find_custom_callable));
+	CHECK_EQ(index, 2);
+}
+
+TEST_CASE("[Array] Test rfind_custom") {
+	Array a1 = build_array(1, 3, 4, 5, 8, 9);
+	// Find last even number.
+	int index = a1.rfind_custom(callable_mp_static(_find_custom_callable));
+	CHECK_EQ(index, 4);
+}
+
 } // namespace TestArray
 
 #endif // TEST_ARRAY_H