Skip to main content

rok_utils/
arr.rs

1//! Array and collection utilities.
2//!
3//! This module provides pure functional helpers for working with slices and vectors,
4//! inspired by Laravel's Collection API.
5//!
6//! # Example
7//!
8//! ```rust
9//! use rok_utils::arr::{map, filter, find};
10//!
11//! let numbers = [1, 2, 3, 4, 5];
12//!
13//! let doubled = map(&numbers, |x| x * 2);
14//! assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
15//!
16//! let evens = filter(&numbers, |x| x % 2 == 0);
17//! assert_eq!(evens, vec![2, 4]);
18//! ```
19
20use std::collections::HashMap;
21
22pub fn map<T, U>(arr: &[T], f: impl Fn(&T) -> U) -> Vec<U> {
23    arr.iter().map(f).collect()
24}
25
26pub fn filter<T: Clone>(arr: &[T], f: impl Fn(&T) -> bool) -> Vec<T> {
27    arr.iter().filter(|x| f(x)).cloned().collect()
28}
29
30pub fn filter_map<T, U>(arr: &[T], f: impl Fn(&T) -> Option<U>) -> Vec<U> {
31    arr.iter().filter_map(f).collect()
32}
33
34pub fn reduce<T, A>(arr: &[T], init: A, f: impl Fn(A, &T) -> A) -> A {
35    arr.iter().fold(init, f)
36}
37
38pub fn chunk<T: Clone>(arr: &[T], size: usize) -> Vec<Vec<T>> {
39    if size == 0 {
40        return vec![arr.to_vec()];
41    }
42    arr.chunks(size).map(|c| c.to_vec()).collect()
43}
44
45pub fn flatten<T: Clone>(arr: &[Vec<T>]) -> Vec<T> {
46    arr.iter().flatten().cloned().collect()
47}
48
49pub fn compact<T: Clone + Default + PartialEq>(arr: &[T]) -> Vec<T> {
50    arr.iter()
51        .filter(|x| **x != T::default())
52        .cloned()
53        .collect()
54}
55
56pub fn take<T: Clone>(arr: &[T], n: usize) -> Vec<T> {
57    arr.iter().take(n).cloned().collect()
58}
59
60pub fn skip<T: Clone>(arr: &[T], n: usize) -> Vec<T> {
61    arr.iter().skip(n).cloned().collect()
62}
63
64pub fn reverse<T: Clone>(arr: &[T]) -> Vec<T> {
65    let mut out: Vec<T> = arr.to_vec();
66    out.reverse();
67    out
68}
69
70pub fn zip<A: Clone, B: Clone>(a: &[A], b: &[B]) -> Vec<(A, B)> {
71    a.iter()
72        .zip(b.iter())
73        .map(|(x, y)| (x.clone(), y.clone()))
74        .collect()
75}
76
77pub fn first<T>(arr: &[T]) -> Option<&T> {
78    arr.first()
79}
80
81pub fn last<T>(arr: &[T]) -> Option<&T> {
82    arr.last()
83}
84
85pub fn get<T>(arr: &[T], index: usize) -> Option<&T> {
86    arr.get(index)
87}
88
89pub fn find<T>(arr: &[T], f: impl Fn(&T) -> bool) -> Option<&T> {
90    arr.iter().find(|x| f(x))
91}
92
93pub fn some<T>(arr: &[T], f: impl Fn(&T) -> bool) -> bool {
94    arr.iter().any(f)
95}
96
97pub fn every<T>(arr: &[T], f: impl Fn(&T) -> bool) -> bool {
98    arr.iter().all(f)
99}
100
101pub fn contains<T: PartialEq>(arr: &[T], value: &T) -> bool {
102    arr.iter().any(|x| x == value)
103}
104
105pub fn group_by<T, K>(arr: &[T], key_fn: impl Fn(&T) -> K) -> HashMap<K, Vec<T>>
106where
107    K: Eq + std::hash::Hash,
108    T: Clone,
109{
110    let mut map: HashMap<K, Vec<T>> = HashMap::new();
111    for item in arr {
112        map.entry(key_fn(item)).or_default().push(item.clone());
113    }
114    map
115}
116
117pub fn key_by<T, K>(arr: &[T], key_fn: impl Fn(&T) -> K) -> HashMap<K, T>
118where
119    K: Eq + std::hash::Hash,
120    T: Clone,
121{
122    let mut map: HashMap<K, T> = HashMap::new();
123    for item in arr {
124        map.insert(key_fn(item), item.clone());
125    }
126    map
127}
128
129pub fn pluck<T, U>(arr: &[T], extractor: impl Fn(&T) -> U) -> Vec<U> {
130    arr.iter().map(extractor).collect()
131}
132
133pub fn where_in<T: PartialEq + Clone>(arr: &[T], values: &[T]) -> Vec<T> {
134    arr.iter().filter(|x| values.contains(x)).cloned().collect()
135}
136
137pub fn count<T>(arr: &[T]) -> usize {
138    arr.len()
139}
140
141pub fn is_empty<T>(arr: &[T]) -> bool {
142    arr.is_empty()
143}
144
145pub fn is_not_empty<T>(arr: &[T]) -> bool {
146    !arr.is_empty()
147}
148
149pub fn unique<T: PartialEq + Clone>(arr: &[T]) -> Vec<T> {
150    let mut seen: Vec<T> = vec![];
151    arr.iter()
152        .filter(|x| {
153            if seen.contains(x) {
154                false
155            } else {
156                seen.push((*x).clone());
157                true
158            }
159        })
160        .cloned()
161        .collect()
162}
163
164pub fn without<T: PartialEq + Clone>(arr: &[T], values: &[T]) -> Vec<T> {
165    arr.iter()
166        .filter(|x| !values.contains(x))
167        .cloned()
168        .collect()
169}
170
171pub fn merge<T: Clone>(a: &[T], b: &[T]) -> Vec<T> {
172    let mut out = a.to_vec();
173    out.extend(b.iter().cloned());
174    out
175}
176
177pub fn intersect<T: PartialEq + Clone>(a: &[T], b: &[T]) -> Vec<T> {
178    a.iter().filter(|x| b.contains(x)).cloned().collect()
179}
180
181pub fn diff<T: PartialEq + Clone>(a: &[T], b: &[T]) -> Vec<T> {
182    a.iter().filter(|x| !b.contains(x)).cloned().collect()
183}
184
185pub fn sort_by<T: Clone>(arr: &[T], cmp: impl Fn(&T, &T) -> std::cmp::Ordering) -> Vec<T> {
186    let mut out = arr.to_vec();
187    out.sort_by(cmp);
188    out
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_map() {
197        let arr = [1, 2, 3];
198        let result = map(&arr, |x| x * 2);
199        assert_eq!(result, vec![2, 4, 6]);
200    }
201
202    #[test]
203    fn test_filter() {
204        let arr = [1, 2, 3, 4];
205        let result = filter(&arr, |x| *x % 2 == 0);
206        assert_eq!(result, vec![2, 4]);
207    }
208
209    #[test]
210    fn test_chunk() {
211        let arr = [1, 2, 3, 4, 5];
212        let result = chunk(&arr, 2);
213        assert_eq!(result, vec![vec![1, 2], vec![3, 4], vec![5]]);
214    }
215
216    #[test]
217    fn test_unique() {
218        let arr = [1, 2, 2, 3, 3, 3, 4];
219        let result = unique(&arr);
220        assert_eq!(result, vec![1, 2, 3, 4]);
221    }
222
223    #[test]
224    fn test_contains() {
225        let arr = [1, 2, 3];
226        assert!(contains(&arr, &2));
227        assert!(!contains(&arr, &5));
228    }
229}