rust_lodash/collection/
transform.rs

1/*!
2Transform methods for Lodash-RS.
3
4This module provides transform methods like `group_by`, `key_by`, `sort_by`, etc.
5These methods are used to reorganize and transform collections.
6*/
7
8use crate::collection::Collection;
9// Note: These imports are kept for future use in error handling and type constraints
10// use crate::utils::{LodashError, Result, ToKey, ToComparable};
11use std::collections::HashMap;
12
13/// Create an object composed of keys generated from the results of running
14/// each element of collection through iteratee. The order of grouped values
15/// is determined by the order they occur in collection.
16/// 
17/// # Examples
18/// 
19/// ```
20/// use rust_lodash::collection::transform::group_by;
21/// use std::collections::HashMap;
22/// 
23/// let numbers = vec![6.1, 4.2, 6.3];
24/// let grouped = group_by(&numbers, |x| (*x as f64).floor() as i32);
25/// assert_eq!(grouped.get(&6), Some(&vec![6.1, 6.3]));
26/// assert_eq!(grouped.get(&4), Some(&vec![4.2]));
27/// ```
28pub fn group_by<T, K, F>(collection: &[T], iteratee: F) -> HashMap<K, Vec<T>>
29where
30    K: std::hash::Hash + Eq,
31    T: Clone,
32    F: Fn(&T) -> K,
33{
34    let mut groups = HashMap::new();
35    for item in collection {
36        let key = iteratee(item);
37        groups.entry(key).or_insert_with(Vec::new).push(item.clone());
38    }
39    groups
40}
41
42/// Create an object composed of keys generated from the results of running
43/// each element of collection through iteratee. The corresponding value of
44/// each key is the last element responsible for generating the key.
45/// 
46/// # Examples
47/// 
48/// ```
49/// use rust_lodash::collection::transform::key_by;
50/// use std::collections::HashMap;
51/// 
52/// let users = vec![
53///     ("john", 30),
54///     ("jane", 25),
55///     ("bob", 35),
56/// ];
57/// let keyed = key_by(&users, |(name, _)| name.to_string());
58/// assert_eq!(keyed.get("john"), Some(&("john", 30)));
59/// ```
60pub fn key_by<T, K, F>(collection: &[T], iteratee: F) -> HashMap<K, T>
61where
62    K: std::hash::Hash + Eq,
63    T: Clone,
64    F: Fn(&T) -> K,
65{
66    let mut keyed = HashMap::new();
67    for item in collection {
68        let key = iteratee(item);
69        keyed.insert(key, item.clone());
70    }
71    keyed
72}
73
74/// Invoke the method at path of each element in collection.
75/// 
76/// # Examples
77/// 
78/// ```
79/// use rust_lodash::collection::transform::invoke;
80/// 
81/// let strings = vec!["hello", "world"];
82/// let uppercased = invoke(&strings, |s| s.to_uppercase());
83/// assert_eq!(uppercased, vec!["HELLO", "WORLD"]);
84/// ```
85pub fn invoke<T, U, F>(collection: &[T], method: F) -> Vec<U>
86where
87    F: Fn(&T) -> U,
88{
89    collection.iter().map(method).collect()
90}
91
92/// Create an array of elements, sorted in ascending order by the results of
93/// running each element in collection through iteratee.
94/// 
95/// # Examples
96/// 
97/// ```
98/// use rust_lodash::collection::transform::sort_by;
99/// 
100/// let users = vec![
101///     ("john", 30),
102///     ("jane", 25),
103///     ("bob", 35),
104/// ];
105/// let sorted = sort_by(&users, |(_, age)| *age);
106/// assert_eq!(sorted[0], ("jane", 25));
107/// assert_eq!(sorted[1], ("john", 30));
108/// assert_eq!(sorted[2], ("bob", 35));
109/// ```
110pub fn sort_by<T, K, F>(collection: &[T], iteratee: F) -> Vec<T>
111where
112    T: Clone,
113    K: Ord,
114    F: Fn(&T) -> K,
115{
116    let mut sorted = collection.to_vec();
117    sorted.sort_by_key(iteratee);
118    sorted
119}
120
121/// This method is like `sort_by` except that it allows specifying the sort
122/// orders of the iteratees to sort by.
123/// 
124/// # Examples
125/// 
126/// ```
127/// use rust_lodash::collection::transform::order_by;
128/// 
129/// let users = vec![
130///     ("john", 30, "engineer"),
131///     ("jane", 25, "designer"),
132///     ("bob", 30, "manager"),
133/// ];
134/// let sorted = order_by(&users, |(_, age, _)| *age, false);
135/// assert_eq!(sorted[0], ("john", 30, "engineer"));
136/// assert_eq!(sorted[1], ("bob", 30, "manager"));
137/// assert_eq!(sorted[2], ("jane", 25, "designer"));
138/// ```
139pub fn order_by<T, K, F>(collection: &[T], iteratee: F, ascending: bool) -> Vec<T>
140where
141    T: Clone,
142    K: Ord,
143    F: Fn(&T) -> K,
144{
145    let mut sorted = collection.to_vec();
146    if ascending {
147        sorted.sort_by_key(iteratee);
148    } else {
149        sorted.sort_by(|a, b| {
150            let key_a = iteratee(a);
151            let key_b = iteratee(b);
152            key_b.cmp(&key_a)
153        });
154    }
155    sorted
156}
157
158/// Collection methods that work on the `Collection` type.
159impl<T> Collection<T> {
160    /// Create an object composed of keys generated from the results of running
161    /// each element through iteratee.
162    /// 
163    /// # Examples
164    /// 
165    /// ```
166    /// use rust_lodash::collection::Collection;
167    /// use std::collections::HashMap;
168    /// 
169    /// let collection = Collection::new(vec![6.1, 4.2, 6.3]);
170    /// let grouped = collection.group_by(|x| (*x as f64).floor() as i32);
171    /// assert_eq!(grouped.get(&6), Some(&vec![6.1, 6.3]));
172    /// ```
173    pub fn group_by<K, F>(&self, iteratee: F) -> HashMap<K, Vec<T>>
174    where
175        K: std::hash::Hash + Eq,
176        T: Clone,
177        F: Fn(&T) -> K,
178    {
179        group_by(&self.data, iteratee)
180    }
181
182    /// Create an object composed of keys generated from the results of running
183    /// each element through iteratee.
184    /// 
185    /// # Examples
186    /// 
187    /// ```
188    /// use rust_lodash::collection::Collection;
189    /// use std::collections::HashMap;
190    /// 
191    /// let collection = Collection::new(vec![
192    ///     ("john", 30),
193    ///     ("jane", 25),
194    ///     ("bob", 35),
195    /// ]);
196    /// let keyed = collection.key_by(|(name, _)| name.to_string());
197    /// assert_eq!(keyed.get("john"), Some(&("john", 30)));
198    /// ```
199    pub fn key_by<K, F>(&self, iteratee: F) -> HashMap<K, T>
200    where
201        K: std::hash::Hash + Eq,
202        T: Clone,
203        F: Fn(&T) -> K,
204    {
205        key_by(&self.data, iteratee)
206    }
207
208    /// Invoke the method at path of each element.
209    /// 
210    /// # Examples
211    /// 
212    /// ```
213    /// use rust_lodash::collection::Collection;
214    /// 
215    /// let collection = Collection::new(vec!["hello", "world"]);
216    /// let uppercased = collection.invoke(|s| s.to_uppercase());
217    /// assert_eq!(uppercased, vec!["HELLO", "WORLD"]);
218    /// ```
219    pub fn invoke<U, F>(&self, method: F) -> Vec<U>
220    where
221        F: Fn(&T) -> U,
222    {
223        invoke(&self.data, method)
224    }
225
226    /// Create an array of elements, sorted in ascending order by the results of
227    /// running each element through iteratee.
228    /// 
229    /// # Examples
230    /// 
231    /// ```
232    /// use rust_lodash::collection::Collection;
233    /// 
234    /// let collection = Collection::new(vec![
235    ///     ("john", 30),
236    ///     ("jane", 25),
237    ///     ("bob", 35),
238    /// ]);
239    /// let sorted = collection.sort_by(|(_, age)| *age);
240    /// assert_eq!(sorted[0], ("jane", 25));
241    /// ```
242    pub fn sort_by<K, F>(&self, iteratee: F) -> Vec<T>
243    where
244        T: Clone,
245        K: Ord,
246        F: Fn(&T) -> K,
247    {
248        sort_by(&self.data, iteratee)
249    }
250
251    /// This method is like `sort_by` except that it allows specifying the sort
252    /// orders of the iteratees to sort by.
253    /// 
254    /// # Examples
255    /// 
256    /// ```
257    /// use rust_lodash::collection::Collection;
258    /// 
259    /// let collection = Collection::new(vec![
260    ///     ("john", 30, "engineer"),
261    ///     ("jane", 25, "designer"),
262    ///     ("bob", 30, "manager"),
263    /// ]);
264    /// let sorted = collection.order_by(|(_, age, _)| *age, false);
265    /// assert_eq!(sorted[0], ("john", 30, "engineer"));
266    /// ```
267    pub fn order_by<K, F>(&self, iteratee: F, ascending: bool) -> Vec<T>
268    where
269        T: Clone,
270        K: Ord,
271        F: Fn(&T) -> K,
272    {
273        order_by(&self.data, iteratee, ascending)
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    // use std::collections::HashMap; // Already imported at module level
281
282    #[test]
283    fn test_group_by() {
284        let numbers = vec![6.1, 4.2, 6.3];
285        let grouped = group_by(&numbers, |x| {
286            #[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)]
287            {
288                (*x as f64).floor() as i32
289            }
290        });
291        assert_eq!(grouped.get(&6), Some(&vec![6.1, 6.3]));
292        assert_eq!(grouped.get(&4), Some(&vec![4.2]));
293    }
294
295    #[test]
296    fn test_key_by() {
297        let users = vec![
298            ("john", 30),
299            ("jane", 25),
300            ("bob", 35),
301        ];
302        let keyed = key_by(&users, |(name, _)| (*name).to_string());
303        assert_eq!(keyed.get("john"), Some(&("john", 30)));
304        assert_eq!(keyed.get("jane"), Some(&("jane", 25)));
305        assert_eq!(keyed.get("bob"), Some(&("bob", 35)));
306    }
307
308    #[test]
309    fn test_invoke() {
310        let strings = vec!["hello", "world"];
311        let uppercased = invoke(&strings, |s| s.to_uppercase());
312        assert_eq!(uppercased, vec!["HELLO", "WORLD"]);
313    }
314
315    #[test]
316    fn test_sort_by() {
317        let users = vec![
318            ("john", 30),
319            ("jane", 25),
320            ("bob", 35),
321        ];
322        let sorted = sort_by(&users, |(_, age)| *age);
323        assert_eq!(sorted[0], ("jane", 25));
324        assert_eq!(sorted[1], ("john", 30));
325        assert_eq!(sorted[2], ("bob", 35));
326    }
327
328    #[test]
329    fn test_order_by_ascending() {
330        let users = vec![
331            ("john", 30),
332            ("jane", 25),
333            ("bob", 35),
334        ];
335        let sorted = order_by(&users, |(_, age)| *age, true);
336        assert_eq!(sorted[0], ("jane", 25));
337        assert_eq!(sorted[1], ("john", 30));
338        assert_eq!(sorted[2], ("bob", 35));
339    }
340
341    #[test]
342    fn test_order_by_descending() {
343        let users = vec![
344            ("john", 30),
345            ("jane", 25),
346            ("bob", 35),
347        ];
348        let sorted = order_by(&users, |(_, age)| *age, false);
349        assert_eq!(sorted[0], ("bob", 35));
350        assert_eq!(sorted[1], ("john", 30));
351        assert_eq!(sorted[2], ("jane", 25));
352    }
353
354    #[test]
355    fn test_collection_group_by() {
356        let collection = Collection::new(vec![6.1, 4.2, 6.3]);
357        let grouped = collection.group_by(|x| {
358            #[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)]
359            {
360                (*x as f64).floor() as i32
361            }
362        });
363        assert_eq!(grouped.get(&6), Some(&vec![6.1, 6.3]));
364    }
365
366    #[test]
367    fn test_collection_key_by() {
368        let collection = Collection::new(vec![
369            ("john", 30),
370            ("jane", 25),
371            ("bob", 35),
372        ]);
373        let keyed = collection.key_by(|(name, _)| (*name).to_string());
374        assert_eq!(keyed.get("john"), Some(&("john", 30)));
375    }
376
377    #[test]
378    fn test_collection_invoke() {
379        let collection = Collection::new(vec!["hello", "world"]);
380        let uppercased = collection.invoke(|s| s.to_uppercase());
381        assert_eq!(uppercased, vec!["HELLO", "WORLD"]);
382    }
383
384    #[test]
385    fn test_collection_sort_by() {
386        let collection = Collection::new(vec![
387            ("john", 30),
388            ("jane", 25),
389            ("bob", 35),
390        ]);
391        let sorted = collection.sort_by(|(_, age)| *age);
392        assert_eq!(sorted[0], ("jane", 25));
393    }
394
395    #[test]
396    fn test_collection_order_by() {
397        let collection = Collection::new(vec![
398            ("john", 30),
399            ("jane", 25),
400            ("bob", 35),
401        ]);
402        let sorted = collection.order_by(|(_, age)| *age, false);
403        assert_eq!(sorted[0], ("bob", 35));
404    }
405
406    #[test]
407    fn test_empty_collection() {
408        let empty: Vec<i32> = vec![];
409        let grouped = group_by(&empty, |x| x % 2);
410        assert!(grouped.is_empty());
411
412        let keyed = key_by(&empty, std::string::ToString::to_string);
413        assert!(keyed.is_empty());
414
415        let invoked = invoke(&empty, |x| x * 2);
416        assert!(invoked.is_empty());
417
418        let sorted = sort_by(&empty, |x| *x);
419        assert!(sorted.is_empty());
420    }
421}