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}