use std::collections::HashMap;
use std::hash::Hash;
pub trait RullstCollection<T> {
fn key_by<K, F>(self, f: F) -> HashMap<K, T>
where
F: Fn(&T) -> K,
K: Hash + Eq;
fn chunk(self, size: usize) -> Vec<Vec<T>>;
fn implode<F>(&self, separator: &str, f: F) -> String
where
F: Fn(&T) -> String;
fn sum_by<N, F>(&self, f: F) -> N
where
F: Fn(&T) -> N,
N: std::iter::Sum;
fn max_by_key<K, F>(&self, f: F) -> Option<&T>
where
F: Fn(&T) -> K,
K: Ord;
fn min_by_key<K, F>(&self, f: F) -> Option<&T>
where
F: Fn(&T) -> K,
K: Ord;
fn collection_resource(&self) -> serde_json::Value
where
T: crate::resource::ApiResource;
}
impl<T> RullstCollection<T> for Vec<T> {
fn key_by<K, F>(self, f: F) -> HashMap<K, T>
where
F: Fn(&T) -> K,
K: Hash + Eq,
{
let mut map = HashMap::with_capacity(self.len());
for item in self {
map.insert(f(&item), item);
}
map
}
fn chunk(self, size: usize) -> Vec<Vec<T>> {
if size == 0 {
return vec![self];
}
let mut chunks = Vec::with_capacity(self.len().div_ceil(size));
let mut current_chunk = Vec::with_capacity(size);
for item in self {
current_chunk.push(item);
if current_chunk.len() == size {
chunks.push(current_chunk);
current_chunk = Vec::with_capacity(size);
}
}
if !current_chunk.is_empty() {
chunks.push(current_chunk);
}
chunks
}
fn implode<F>(&self, separator: &str, f: F) -> String
where
F: Fn(&T) -> String,
{
let mut result = String::new();
let mut iter = self.iter();
if let Some(first) = iter.next() {
result.push_str(&f(first));
for item in iter {
result.push_str(separator);
result.push_str(&f(item));
}
}
result
}
fn sum_by<N, F>(&self, f: F) -> N
where
F: Fn(&T) -> N,
N: std::iter::Sum,
{
self.iter().map(f).sum()
}
fn max_by_key<K, F>(&self, f: F) -> Option<&T>
where
F: Fn(&T) -> K,
K: Ord,
{
self.iter().max_by_key(|item| f(*item))
}
fn min_by_key<K, F>(&self, f: F) -> Option<&T>
where
F: Fn(&T) -> K,
K: Ord,
{
self.iter().min_by_key(|item| f(*item))
}
fn collection_resource(&self) -> serde_json::Value
where
T: crate::resource::ApiResource,
{
crate::resource::ResourceCollection::new(self).resolve()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_by() {
let v = vec![(1u32, "a"), (2, "b"), (3, "c")];
let map = v.key_by(|(k, _)| *k);
assert_eq!(map[&1].1, "a");
assert_eq!(map[&3].1, "c");
}
#[test]
fn test_chunk_even() {
let v = vec![1, 2, 3, 4];
let chunks = v.chunk(2);
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0], vec![1, 2]);
assert_eq!(chunks[1], vec![3, 4]);
}
#[test]
fn test_chunk_with_remainder() {
let v = vec![1, 2, 3, 4, 5];
let chunks = v.chunk(2);
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[2], vec![5]);
}
#[test]
fn test_chunk_zero_returns_all() {
let v = vec![1, 2, 3];
let chunks = v.chunk(0);
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], vec![1, 2, 3]);
}
#[test]
fn test_implode() {
let v = vec![1, 2, 3];
let result = v.implode(", ", |n| n.to_string());
assert_eq!(result, "1, 2, 3");
}
#[test]
fn test_implode_single_element() {
let v = vec![42];
let result = v.implode(", ", |n| n.to_string());
assert_eq!(result, "42");
}
#[test]
fn test_sum_by() {
let v = vec![1, 2, 3, 4];
let sum: i32 = v.sum_by(|n| *n);
assert_eq!(sum, 10);
}
#[test]
fn test_max_by_key() {
let v = vec![3, 1, 4, 1, 5, 9];
let max = v.max_by_key(|n| *n);
assert_eq!(max, Some(&9));
}
#[test]
fn test_min_by_key() {
let v = vec![3, 1, 4, 1, 5, 9];
let min = v.min_by_key(|n| *n);
assert_eq!(min, Some(&1));
}
#[test]
fn test_empty_collection() {
let v: Vec<i32> = vec![];
assert!(v.max_by_key(|n| *n).is_none());
assert!(v.min_by_key(|n| *n).is_none());
let sum: i32 = v.sum_by(|n| *n);
assert_eq!(sum, 0);
}
}