godot_core/builtin/collections/array_functional_ops.rs
1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use crate::builtin::{Array, Callable, VarArray, Variant, to_usize};
9use crate::meta::{AsArg, Element};
10use crate::{meta, sys};
11
12/// Immutable, functional-programming operations for `Array`, based on Godot callables.
13///
14/// Returned by [`Array::functional_ops()`].
15///
16/// These methods exist to provide parity with Godot, e.g. when porting GDScript code to Rust. However, they come with several disadvantages
17/// compared to Rust's [iterator adapters](https://doc.rust-lang.org/stable/core/iter/index.html#adapters):
18/// - Not type-safe: callables are dynamically typed, so you need to double-check signatures. Godot may misinterpret returned values
19/// (e.g. predicates apply to any "truthy" values, not just booleans).
20/// - Slower: dispatching through callables is typically more costly than iterating over variants, especially since every call involves multiple
21/// variant conversions, too. Combining multiple operations like `filter().map()` is very expensive due to intermediate allocations.
22/// - Less composable/flexible: Godot's `map()` always returns an untyped array, even if the input is typed and unchanged by the mapping.
23/// Rust's `collect()` on the other hand gives you control over the output type. Chaining iterators can apply multiple transformations lazily.
24///
25/// In many cases, it is thus better to use [`Array::iter_shared()`] combined with iterator adapters. Check the individual method docs of
26/// this struct for concrete alternatives.
27pub struct ArrayFunctionalOps<'a, T: Element> {
28 array: &'a Array<T>,
29}
30
31impl<'a, T: Element> ArrayFunctionalOps<'a, T> {
32 pub(super) fn new(owner: &'a Array<T>) -> Self {
33 Self { array: owner }
34 }
35
36 /// Returns a new array containing only the elements for which the callable returns a truthy value.
37 ///
38 /// **Rust alternatives:** [`Iterator::filter()`].
39 ///
40 /// The callable has signature `fn(T) -> bool`.
41 ///
42 /// # Example
43 /// ```no_run
44 /// # use godot::prelude::*;
45 /// let array = iarray![1, 2, 3, 4, 5];
46 /// let even = array.functional_ops().filter(&Callable::from_fn("is_even", |args| {
47 /// args[0].to::<i64>() % 2 == 0
48 /// }));
49 /// assert_eq!(even, array![2, 4]);
50 /// ```
51 #[must_use]
52 pub fn filter(&self, callable: &Callable) -> Array<T> {
53 self.array.as_inner().filter(callable).cast_array::<T>()
54 }
55
56 /// Returns a new untyped array with each element transformed by the callable.
57 ///
58 /// **Rust alternatives:** [`Iterator::map()`].
59 ///
60 /// The callable has signature `fn(T) -> Variant`. Since the transformation can change the element type, this method returns
61 /// a `VarArray` (untyped array).
62 ///
63 /// # Example
64 /// ```no_run
65 /// # use godot::prelude::*;
66 /// let array = iarray![1.1, 1.5, 1.9];
67 /// let rounded = array.functional_ops().map(&Callable::from_fn("round", |args| {
68 /// args[0].to::<f64>().round() as i64
69 /// }));
70 /// assert_eq!(rounded, varray![1, 2, 2]);
71 /// ```
72 #[must_use]
73 pub fn map(&self, callable: &Callable) -> VarArray {
74 self.array.as_inner().map(callable).cast_array()
75 }
76
77 /// Reduces the array to a single value by iteratively applying the callable.
78 ///
79 /// **Rust alternatives:** [`Iterator::fold()`] or [`Iterator::reduce()`].
80 ///
81 /// The callable takes two arguments: the accumulator and the current element.
82 /// It returns the new accumulator value. The process starts with `initial` as the accumulator.
83 ///
84 /// # Example
85 /// ```no_run
86 /// # use godot::prelude::*;
87 /// let array = iarray![1, 2, 3, 4];
88 /// let sum = array.functional_ops().reduce(
89 /// &Callable::from_fn("sum", |args| {
90 /// args[0].to::<i64>() + args[1].to::<i64>()
91 /// }),
92 /// &0.to_variant()
93 /// );
94 /// assert_eq!(sum, 10.to_variant());
95 /// ```
96 #[must_use]
97 pub fn reduce(&self, callable: &Callable, initial: &Variant) -> Variant {
98 self.array.as_inner().reduce(callable, initial)
99 }
100
101 /// Returns `true` if the callable returns a truthy value for at least one element.
102 ///
103 /// **Rust alternatives:** [`Iterator::any()`].
104 ///
105 /// The callable has signature `fn(element) -> bool`.
106 ///
107 /// # Example
108 /// ```no_run
109 /// # use godot::prelude::*;
110 /// let array = iarray![1, 2, 3, 4];
111 /// let any_even = array.functional_ops().any(&Callable::from_fn("is_even", |args| {
112 /// args[0].to::<i64>() % 2 == 0
113 /// }));
114 /// assert!(any_even);
115 /// ```
116 pub fn any(&self, callable: &Callable) -> bool {
117 self.array.as_inner().any(callable)
118 }
119
120 /// Returns `true` if the callable returns a truthy value for all elements.
121 ///
122 /// **Rust alternatives:** [`Iterator::all()`].
123 ///
124 /// The callable has signature `fn(element) -> bool`.
125 ///
126 /// # Example
127 /// ```no_run
128 /// # use godot::prelude::*;
129 /// let array = iarray![2, 4, 6];
130 /// let all_even = array.functional_ops().all(&Callable::from_fn("is_even", |args| {
131 /// args[0].to::<i64>() % 2 == 0
132 /// }));
133 /// assert!(all_even);
134 /// ```
135 pub fn all(&self, callable: &Callable) -> bool {
136 self.array.as_inner().all(callable)
137 }
138
139 /// Finds the index of the first element matching a custom predicate.
140 ///
141 /// **Rust alternatives:** [`Iterator::position()`].
142 ///
143 /// The callable has signature `fn(element) -> bool`.
144 ///
145 /// Returns the index of the first element for which the callable returns a truthy value, starting from `from`.
146 /// If no element matches, returns `None`.
147 ///
148 /// # Example
149 /// ```no_run
150 /// # use godot::prelude::*;
151 /// let array = iarray![1, 2, 3, 4, 5];
152 /// let is_even = Callable::from_fn("is_even", |args| {
153 /// args[0].to::<i64>() % 2 == 0
154 /// });
155 /// assert_eq!(array.functional_ops().find_custom(&is_even, None), Some(1)); // value 2
156 /// assert_eq!(array.functional_ops().find_custom(&is_even, Some(2)), Some(3)); // value 4
157 /// ```
158 #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
159 pub fn find_custom(&self, callable: &Callable, from: Option<usize>) -> Option<usize> {
160 let from = from.map(|i| i as i64).unwrap_or(0);
161 let found_index = self.array.as_inner().find_custom(callable, from);
162
163 sys::found_to_option(found_index)
164 }
165
166 /// Finds the index of the last element matching a custom predicate, searching backwards.
167 ///
168 /// **Rust alternatives:** [`Iterator::rposition()`].
169 ///
170 /// The callable has signature `fn(element) -> bool`.
171 ///
172 /// Returns the index of the last element for which the callable returns a truthy value, searching backwards from `from`.
173 /// If no element matches, returns `None`.
174 ///
175 /// # Example
176 /// ```no_run
177 /// # use godot::prelude::*;
178 /// let array = iarray![1, 2, 3, 4, 5];
179 /// let is_even = Callable::from_fn("is_even", |args| {
180 /// args[0].to::<i64>() % 2 == 0
181 /// });
182 /// assert_eq!(array.functional_ops().rfind_custom(&is_even, None), Some(3)); // value 4
183 /// assert_eq!(array.functional_ops().rfind_custom(&is_even, Some(2)), Some(1)); // value 2
184 /// ```
185 #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
186 pub fn rfind_custom(&self, callable: &Callable, from: Option<usize>) -> Option<usize> {
187 let from = from.map(|i| i as i64).unwrap_or(-1);
188 let found_index = self.array.as_inner().rfind_custom(callable, from);
189
190 sys::found_to_option(found_index)
191 }
192
193 /// Finds the index of a value in a sorted array using binary search, with `Callable` custom predicate.
194 ///
195 /// The callable `pred` takes two elements `(a, b)` and should return if `a < b` (strictly less).
196 /// For a type-safe version, check out [`Array::bsearch_by()`].
197 ///
198 /// If the value is not present in the array, returns the insertion index that would maintain sorting order.
199 ///
200 /// Calling `bsearch_custom()` on an unsorted array results in unspecified behavior. Consider using
201 /// [`AnyArray::sort_unstable_custom()`][crate::builtin::AnyArray::sort_unstable_custom()] beforehand.
202 /// to ensure the sorting order is compatible with your callable's ordering.
203 pub fn bsearch_custom(&self, value: impl AsArg<T>, pred: &Callable) -> usize {
204 meta::arg_into_ref!(value: T);
205
206 to_usize(
207 self.array
208 .as_inner()
209 .bsearch_custom(&value.to_variant(), pred, true),
210 )
211 }
212}