Created
February 23, 2025 12:45
-
-
Save BSN4/02e2d9ff20cc9b1c1ef40d647a70938e to your computer and use it in GitHub Desktop.
laravel collection in rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::{HashMap, HashSet}; | |
use std::hash::Hash; | |
pub struct Collection<'a, T> { | |
items: Vec<&'a T>, | |
} | |
impl<'a, T> Collection<'a, T> { | |
pub fn new(items: &'a [T]) -> Self { | |
Self { | |
items: items.iter().collect(), | |
} | |
} | |
// Basic operations | |
pub fn filter(self, predicate: impl Fn(&T) -> bool) -> Self { | |
Self { | |
items: self | |
.items | |
.into_iter() | |
.filter(|item| predicate(item)) | |
.collect(), | |
} | |
} | |
pub fn filter_map<B>(self, f: impl FnMut(&T) -> Option<&'a B>) -> Collection<'a, B> { | |
Collection { | |
items: self.items.into_iter().filter_map(f).collect(), | |
} | |
} | |
pub fn map<B>(self, f: impl FnMut(&T) -> &'a B) -> Collection<'a, B> { | |
Collection { | |
items: self.items.into_iter().map(f).collect(), | |
} | |
} | |
// Laravel-style operations | |
pub fn chunk(self, size: usize) -> Vec<Collection<'a, T>> { | |
self.items | |
.chunks(size) | |
.map(|chunk| Collection { | |
items: chunk.to_vec(), | |
}) | |
.collect() | |
} | |
pub fn take(self, limit: usize) -> Self { | |
Self { | |
items: self.items.into_iter().take(limit).collect(), | |
} | |
} | |
pub fn skip(self, count: usize) -> Self { | |
Self { | |
items: self.items.into_iter().skip(count).collect(), | |
} | |
} | |
pub fn unique_by<K: Hash + Eq>(self, key_fn: impl Fn(&T) -> K) -> Self { | |
let mut seen = HashSet::new(); | |
Self { | |
items: self | |
.items | |
.into_iter() | |
.filter(|item| seen.insert(key_fn(item))) | |
.collect(), | |
} | |
} | |
pub fn unique(self) -> Self | |
where | |
T: Hash + Eq, | |
{ | |
let mut seen = HashSet::new(); | |
Self { | |
items: self | |
.items | |
.into_iter() | |
.filter(|item| seen.insert(*item)) | |
.collect(), | |
} | |
} | |
pub fn sort_by<K: Ord>(self, key_fn: impl Fn(&T) -> K) -> Self { | |
let mut items = self.items; | |
items.sort_by_key(|item| key_fn(item)); | |
Self { items } | |
} | |
pub fn sort_by_desc<K: Ord>(self, key_fn: impl Fn(&T) -> K) -> Self { | |
let mut items = self.items; | |
items.sort_by_key(|item| std::cmp::Reverse(key_fn(item))); | |
Self { items } | |
} | |
pub fn reverse(self) -> Self { | |
let mut items = self.items; | |
items.reverse(); | |
Self { items } | |
} | |
pub fn group_by<K>(self, key_fn: impl Fn(&T) -> K) -> GroupedCollection<'a, K, T> | |
where | |
K: Eq + Hash, | |
{ | |
let groups = self | |
.items | |
.into_iter() | |
.fold(HashMap::new(), |mut acc, item| { | |
acc.entry(key_fn(item)).or_insert_with(Vec::new).push(item); | |
acc | |
}); | |
GroupedCollection { groups } | |
} | |
// Reduction operations | |
pub fn reduce<B>(self, init: B, f: impl Fn(B, &T) -> B) -> B { | |
self.items.into_iter().fold(init, f) | |
} | |
pub fn each(self, f: impl Fn(&T)) -> Self { | |
self.items.iter().for_each(|item| f(item)); | |
self | |
} | |
pub fn tap<F>(self, f: F) -> Self | |
where | |
F: FnOnce(&Collection<'a, T>), | |
{ | |
f(&self); | |
self | |
} | |
// Set operations | |
pub fn intersect(self, other: Collection<'a, T>) -> Self | |
where | |
T: Eq + Hash, | |
{ | |
let other_set: HashSet<_> = other.items.into_iter().collect(); | |
Self { | |
items: self | |
.items | |
.into_iter() | |
.filter(|item| other_set.contains(item)) | |
.collect(), | |
} | |
} | |
pub fn diff(self, other: Collection<'a, T>) -> Self | |
where | |
T: Eq + Hash, | |
{ | |
let other_set: HashSet<_> = other.items.into_iter().collect(); | |
Self { | |
items: self | |
.items | |
.into_iter() | |
.filter(|item| !other_set.contains(item)) | |
.collect(), | |
} | |
} | |
pub fn union(self, other: Collection<'a, T>) -> Self | |
where | |
T: Eq + Hash, | |
{ | |
let mut seen = HashSet::new(); | |
let mut items = Vec::new(); | |
for item in self.items.into_iter().chain(other.items.into_iter()) { | |
if seen.insert(item) { | |
items.push(item); | |
} | |
} | |
Self { items } | |
} | |
// Aggregation operations | |
pub fn contains(&self, predicate: impl Fn(&T) -> bool) -> bool { | |
self.items.iter().any(|item| predicate(item)) | |
} | |
pub fn every(&self, predicate: impl Fn(&T) -> bool) -> bool { | |
self.items.iter().all(|item| predicate(item)) | |
} | |
pub fn sum_by<R>(self, f: impl Fn(&T) -> R) -> R | |
where | |
R: Default + std::ops::AddAssign, | |
{ | |
self.items.into_iter().fold(R::default(), |mut acc, item| { | |
acc += f(item); | |
acc | |
}) | |
} | |
pub fn average_by(self, f: impl Fn(&T) -> f64) -> Option<f64> { | |
if self.items.is_empty() { | |
None | |
} else { | |
let len = self.items.len(); | |
let sum: f64 = self.items.into_iter().map(|item| f(item)).sum(); | |
Some(sum / len as f64) | |
} | |
} | |
pub fn median_by<R>(self, f: impl Fn(&T) -> R) -> Option<R> | |
where | |
R: Ord + Clone, | |
{ | |
if self.items.is_empty() { | |
return None; | |
} | |
let mut values: Vec<_> = self.items.into_iter().map(|item| f(item)).collect(); | |
values.sort(); | |
let mid = values.len() / 2; | |
Some(values[mid].clone()) | |
} | |
// Terminal operations | |
pub fn get(self) -> Vec<&'a T> { | |
self.items | |
} | |
pub fn all(self) -> Vec<&'a T> { | |
self.items | |
} | |
pub fn first(self) -> Option<&'a T> { | |
self.items.into_iter().next() | |
} | |
pub fn last(self) -> Option<&'a T> { | |
self.items.into_iter().last() | |
} | |
pub fn nth(self, n: usize) -> Option<&'a T> { | |
self.items.into_iter().nth(n) | |
} | |
pub fn count(self) -> usize { | |
self.items.len() | |
} | |
pub fn is_empty(&self) -> bool { | |
self.items.is_empty() | |
} | |
// Pagination support | |
pub fn paginate(self, page: usize, per_page: usize) -> PaginatedCollection<'a, T> { | |
let total = self.items.len(); | |
let items = self | |
.items | |
.into_iter() | |
.skip((page - 1) * per_page) | |
.take(per_page) | |
.collect(); | |
PaginatedCollection { | |
items, | |
total, | |
per_page, | |
current_page: page, | |
} | |
} | |
} | |
pub struct GroupedCollection<'a, K, T> { | |
groups: HashMap<K, Vec<&'a T>>, | |
} | |
impl<'a, K, T> GroupedCollection<'a, K, T> | |
where | |
K: Eq + Hash, | |
{ | |
pub fn all(self) -> HashMap<K, Vec<&'a T>> { | |
self.groups | |
} | |
pub fn get(self, key: K) -> Option<Vec<&'a T>> { | |
self.groups.get(&key).cloned() | |
} | |
pub fn map<B>(self, f: impl Fn(Vec<&'a T>) -> B) -> HashMap<K, B> { | |
self.groups.into_iter().map(|(k, v)| (k, f(v))).collect() | |
} | |
} | |
pub struct PaginatedCollection<'a, T> { | |
items: Vec<&'a T>, | |
total: usize, | |
per_page: usize, | |
current_page: usize, | |
} | |
impl<'a, T> PaginatedCollection<'a, T> { | |
pub fn items(self) -> Vec<&'a T> { | |
self.items | |
} | |
pub fn total(&self) -> usize { | |
self.total | |
} | |
pub fn per_page(&self) -> usize { | |
self.per_page | |
} | |
pub fn current_page(&self) -> usize { | |
self.current_page | |
} | |
pub fn total_pages(&self) -> usize { | |
(self.total + self.per_page - 1) / self.per_page | |
} | |
pub fn has_more_pages(&self) -> bool { | |
self.current_page < self.total_pages() | |
} | |
} | |
#[derive(Debug, Default)] | |
pub struct QueryableCollection<T> { | |
items: Vec<T>, | |
} | |
impl<T> QueryableCollection<T> { | |
pub fn new() -> Self { | |
Self { items: Vec::new() } | |
} | |
pub fn with_capacity(capacity: usize) -> Self { | |
Self { | |
items: Vec::with_capacity(capacity), | |
} | |
} | |
pub fn add(&mut self, item: T) { | |
self.items.push(item); | |
} | |
pub fn query(&self) -> Collection<T> { | |
Collection::new(&self.items) | |
} | |
pub fn len(&self) -> usize { | |
self.items.len() | |
} | |
pub fn is_empty(&self) -> bool { | |
self.items.is_empty() | |
} | |
} | |
// Example usage | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[derive(Debug, PartialEq)] | |
struct Flight { | |
registration: String, | |
aircraft_type: String, | |
duration: i32, | |
fuel: f64, | |
} | |
#[test] | |
fn test_laravel_style_operations() { | |
let mut flights = QueryableCollection::new(); | |
let flight1 = Flight { | |
registration: "N-12345".to_string(), | |
aircraft_type: "C172".to_string(), | |
duration: 120, | |
fuel: 45.5, | |
}; | |
let flight2 = Flight { | |
registration: "N-54321".to_string(), | |
aircraft_type: "C182".to_string(), | |
duration: 90, | |
fuel: 35.0, | |
}; | |
flights.add(flight1); | |
flights.add(flight2); | |
// Test unique | |
let unique_types = flights | |
.query() | |
.unique_by(|f| f.aircraft_type.clone()) | |
.count(); | |
assert_eq!(unique_types, 2); | |
// Test sorting | |
let sorted = flights.query().sort_by(|f| f.duration).get(); | |
assert_eq!(sorted[0].duration, 90); | |
// Test pagination | |
let page = flights.query().paginate(1, 1); | |
assert_eq!(page.items().len(), 1); | |
// assert_eq!(page.total_pages(), 2); page moved to .items() in prev statment can't be used again | |
// Test chaining multiple operations | |
let result = flights | |
.query() | |
.filter(|f| f.duration > 60) | |
.sort_by_desc(|f| f.duration) | |
.take(1) | |
.get(); | |
assert_eq!(result.len(), 1); | |
assert_eq!(result[0].duration, 120); | |
// Test tap for debugging | |
flights.query().filter(|f| f.duration > 100).get(); | |
} | |
} | |
fn main() { | |
println!("....."); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment