use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::hash::Hash;
#[derive(Debug)]
pub struct Distinct<I: Iterator>
where
I::Item: Clone,
{
iter: I,
seen: HashSet<I::Item>,
}
impl<I> Iterator for Distinct<I>
where
I: Iterator,
I::Item: Eq + Hash + Clone,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
loop {
let item = self.iter.next()?;
if self.seen.insert(item.clone()) {
return Some(item);
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (_, upper) = self.iter.size_hint();
(0, upper)
}
}
#[derive(Debug)]
pub struct WhenEmpty<I, F>
where
I: Iterator,
F: IntoIterator<Item = I::Item>,
{
phase: WhenEmptyPhase<I, F::IntoIter>,
fallback: Option<F>,
}
#[derive(Debug)]
enum WhenEmptyPhase<S, T> {
Source(S, bool),
Fallback(T),
Done,
}
impl<I, F> Iterator for WhenEmpty<I, F>
where
I: Iterator,
F: IntoIterator<Item = I::Item>,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
match &mut self.phase {
WhenEmptyPhase::Source(source, yielded) => match source.next() {
Some(item) => {
*yielded = true;
Some(item)
}
None => {
if *yielded {
self.phase = WhenEmptyPhase::Done;
return None;
}
match self.fallback.take() {
Some(fallback) => {
let mut iter = fallback.into_iter();
let item = iter.next();
self.phase = WhenEmptyPhase::Fallback(iter);
item
}
None => {
self.phase = WhenEmptyPhase::Done;
None
}
}
}
},
WhenEmptyPhase::Fallback(iter) => iter.next(),
WhenEmptyPhase::Done => None,
}
}
}
#[derive(Debug)]
pub struct Repeat<I: Iterator>
where
I::Item: Clone,
{
source: I,
cache: Vec<I::Item>,
times: usize,
round: usize,
pos: usize,
}
impl<I> Iterator for Repeat<I>
where
I: Iterator,
I::Item: Clone,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
if self.times == 0 {
return None;
}
loop {
if self.round == 0 {
if let Some(item) = self.source.next() {
self.cache.push(item.clone());
return Some(item);
}
self.round = 1;
self.pos = 0;
}
if self.round >= self.times {
return None;
}
if let Some(item) = self.cache.get(self.pos) {
self.pos += 1;
return Some(item.clone());
}
self.round += 1;
self.pos = 0;
}
}
}
enum Phase<I: Iterator, O> {
Source(I),
Emitting(std::vec::IntoIter<O>),
Done,
}
impl<I, O> Phase<I, O>
where
I: Iterator,
{
fn next_with(&mut self, mut build: impl FnMut(I) -> Vec<O>) -> Option<O> {
if matches!(self, Phase::Source(_)) {
let src = match std::mem::replace(self, Phase::Done) {
Phase::Source(i) => i,
_ => unreachable!("guarded by matches! above"),
};
*self = Phase::Emitting(build(src).into_iter());
}
match self {
Phase::Emitting(it) => it.next(),
_ => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Phase::Emitting(it) => it.size_hint(),
_ => (0, None),
}
}
}
#[derive(Debug)]
pub struct DistinctBy<I, K, F>
where
I: Iterator,
{
iter: I,
key: F,
seen: HashSet<K>,
}
impl<I, K, F> Iterator for DistinctBy<I, K, F>
where
I: Iterator,
K: Eq + Hash,
F: FnMut(&I::Item) -> K,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
self.iter
.by_ref()
.find(|item| self.seen.insert((self.key)(item)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.iter.size_hint().1)
}
}
pub struct GroupByStable<I, K, F>
where
I: Iterator,
{
phase: Phase<I, (K, Vec<I::Item>)>,
key: F,
}
impl<I, K, F> Iterator for GroupByStable<I, K, F>
where
I: Iterator,
K: Eq + Hash + Clone,
F: FnMut(&I::Item) -> K,
{
type Item = (K, Vec<I::Item>);
fn next(&mut self) -> Option<Self::Item> {
self.phase.next_with(|src| {
let mut index: HashMap<K, usize> = HashMap::new();
let mut out: Vec<(K, Vec<I::Item>)> = Vec::new();
for item in src {
let k = (self.key)(&item);
match index.get(&k) {
Some(&i) => out[i].1.push(item),
None => {
index.insert(k.clone(), out.len());
out.push((k, vec![item]));
}
}
}
out
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.phase.size_hint()
}
}
pub struct Combine<I, J, K, KL, KR>
where
I: Iterator,
J: Iterator,
{
left: I,
key_left: KL,
right: J,
key_right: KR,
indexed: bool,
idx: HashMap<K, Vec<J::Item>>,
pending: Option<(I::Item, K, usize)>,
}
impl<I, J, K, KL, KR> Iterator for Combine<I, J, K, KL, KR>
where
I: Iterator,
J: Iterator,
K: Eq + Hash + Clone,
KL: FnMut(&I::Item) -> K,
KR: FnMut(&J::Item) -> K,
I::Item: Clone,
J::Item: Clone,
{
type Item = (I::Item, J::Item);
fn next(&mut self) -> Option<Self::Item> {
if !self.indexed {
for r in self.right.by_ref() {
self.idx.entry((self.key_right)(&r)).or_default().push(r);
}
self.indexed = true;
}
loop {
if self.pending.is_none() {
loop {
let l = self.left.next()?;
let k = (self.key_left)(&l);
if self.idx.get(&k).is_some_and(|b| !b.is_empty()) {
self.pending = Some((l, k, 0));
break;
}
}
}
let (left, key, pos) = self.pending.take().expect("pending was just set");
let bucket = &self.idx[&key];
if pos < bucket.len() {
let pair = (left.clone(), bucket[pos].clone());
self.pending = Some((left, key, pos + 1));
return Some(pair);
}
}
}
}
pub struct CombineJoin<I, T, K, F>
where
I: Iterator,
{
phase: Phase<I, Vec<T>>,
key: F,
_k: std::marker::PhantomData<fn(&T) -> K>,
}
impl<I, T, K, F> Iterator for CombineJoin<I, T, K, F>
where
I: Iterator,
I::Item: IntoIterator<Item = T>,
K: Eq + Hash,
T: Clone,
F: FnMut(&T) -> K,
{
type Item = Vec<T>;
fn next(&mut self) -> Option<Vec<T>> {
self.phase.next_with(|src| {
let mut acc: Vec<Vec<T>> = Vec::new();
let mut first = true;
for iterable in src {
let items: Vec<T> = iterable.into_iter().collect();
if first {
first = false;
acc = items.into_iter().map(|item| vec![item]).collect();
continue;
}
if acc.is_empty() {
continue;
}
let mut idx: HashMap<K, Vec<T>> = HashMap::new();
for item in items {
idx.entry((self.key)(&item)).or_default().push(item);
}
let mut next_acc = Vec::new();
for row in acc {
if let Some(bucket) = idx.get(&(self.key)(&row[0])) {
for item in bucket {
let mut grown = row.clone();
grown.push(item.clone());
next_acc.push(grown);
}
}
}
acc = next_acc;
}
acc
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.phase.size_hint()
}
}
pub struct Sorted<I: Iterator> {
phase: Phase<I, I::Item>,
}
impl<I> Iterator for Sorted<I>
where
I: Iterator,
I::Item: Ord,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
self.phase.next_with(|src| {
let mut out: Vec<I::Item> = src.collect();
out.sort();
out
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.phase.size_hint()
}
}
pub struct SortWith<I: Iterator, F> {
phase: Phase<I, I::Item>,
compare: F,
}
impl<I, F> Iterator for SortWith<I, F>
where
I: Iterator,
F: FnMut(&I::Item, &I::Item) -> Ordering,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
self.phase.next_with(|src| {
let mut out: Vec<I::Item> = src.collect();
out.sort_by(&mut self.compare);
out
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.phase.size_hint()
}
}
pub struct SortBy<I: Iterator, K, F> {
phase: Phase<I, I::Item>,
key: F,
descending: bool,
_k: std::marker::PhantomData<fn(&I::Item) -> K>,
}
impl<I, K, F> Iterator for SortBy<I, K, F>
where
I: Iterator,
K: Ord,
F: FnMut(&I::Item) -> K,
{
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
self.phase.next_with(|src| {
let mut out: Vec<I::Item> = src.collect();
if self.descending {
out.sort_by_key(|item| std::cmp::Reverse((self.key)(item)));
} else {
out.sort_by_key(&mut self.key);
}
out
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.phase.size_hint()
}
}
pub trait FluentIter: Iterator + Sized {
fn group_by_stable<K, F>(self, key: F) -> GroupByStable<Self, K, F>
where
K: Eq + Hash + Clone,
F: FnMut(&Self::Item) -> K,
{
GroupByStable {
phase: Phase::Source(self),
key,
}
}
fn combine<J, K, KL, KR>(
self,
other: J,
key_left: KL,
key_right: KR,
) -> Combine<Self, J::IntoIter, K, KL, KR>
where
J: IntoIterator,
K: Eq + Hash + Clone,
KL: FnMut(&Self::Item) -> K,
KR: FnMut(&J::Item) -> K,
Self::Item: Clone,
J::Item: Clone,
{
Combine {
left: self,
key_left,
right: other.into_iter(),
key_right,
indexed: false,
idx: HashMap::new(),
pending: None,
}
}
fn combine_join<T, K, F>(self, key: F) -> CombineJoin<Self, T, K, F>
where
Self::Item: IntoIterator<Item = T>,
K: Eq + Hash,
T: Clone,
F: FnMut(&T) -> K,
{
CombineJoin {
phase: Phase::Source(self),
key,
_k: std::marker::PhantomData,
}
}
fn distinct(self) -> Distinct<Self>
where
Self::Item: Eq + Hash + Clone,
{
Distinct {
iter: self,
seen: HashSet::new(),
}
}
fn distinct_by<K, F>(self, key: F) -> DistinctBy<Self, K, F>
where
K: Eq + Hash,
F: FnMut(&Self::Item) -> K,
{
DistinctBy {
iter: self,
key,
seen: HashSet::new(),
}
}
#[allow(clippy::wrong_self_convention)]
fn is_distinct(mut self) -> bool
where
Self::Item: Eq + Hash,
{
let mut seen = HashSet::new();
self.all(|item| seen.insert(item))
}
#[allow(clippy::wrong_self_convention)]
fn is_distinct_by<K>(mut self, mut key: impl FnMut(&Self::Item) -> K) -> bool
where
K: Eq + Hash,
{
let mut seen = HashSet::new();
self.all(|item| seen.insert(key(&item)))
}
fn when_empty<F>(self, fallback: F) -> WhenEmpty<Self, F>
where
F: IntoIterator<Item = Self::Item>,
{
WhenEmpty {
phase: WhenEmptyPhase::Source(self, false),
fallback: Some(fallback),
}
}
fn repeat(self, times: usize) -> Repeat<Self>
where
Self::Item: Clone,
{
Repeat {
source: self,
cache: Vec::new(),
times,
round: 0,
pos: 0,
}
}
fn to_map_by<K, V>(
self,
mut key: impl FnMut(&Self::Item) -> K,
mut value: impl FnMut(&Self::Item) -> V,
) -> HashMap<K, V>
where
K: Eq + Hash,
{
self.map(|item| (key(&item), value(&item))).collect()
}
fn to_map_chain<K1, K2>(
self,
mut key1: impl FnMut(&Self::Item) -> K1,
mut key2: impl FnMut(&Self::Item) -> K2,
) -> HashMap<K1, HashMap<K2, Vec<Self::Item>>>
where
K1: Eq + Hash,
K2: Eq + Hash,
{
let mut out: HashMap<K1, HashMap<K2, Vec<Self::Item>>> = HashMap::new();
for item in self {
out.entry(key1(&item))
.or_default()
.entry(key2(&item))
.or_default()
.push(item);
}
out
}
fn to_map_chain_reduce<K1, K2, A>(
self,
key1: impl FnMut(&Self::Item) -> K1,
key2: impl FnMut(&Self::Item) -> K2,
init: A,
mut reduce: impl FnMut(A, Self::Item) -> A,
) -> HashMap<K1, HashMap<K2, A>>
where
K1: Eq + Hash,
K2: Eq + Hash,
A: Clone,
{
self.to_map_chain(key1, key2)
.into_iter()
.map(|(k1, inner)| {
(
k1,
inner
.into_iter()
.map(|(k2, items)| {
let acc = items.into_iter().fold(init.clone(), &mut reduce);
(k2, acc)
})
.collect(),
)
})
.collect()
}
fn sorted(self) -> Sorted<Self>
where
Self::Item: Ord,
{
Sorted {
phase: Phase::Source(self),
}
}
fn sort_with<F>(self, compare: F) -> SortWith<Self, F>
where
F: FnMut(&Self::Item, &Self::Item) -> Ordering,
{
SortWith {
phase: Phase::Source(self),
compare,
}
}
fn sort_by<K, F>(self, key: F) -> SortBy<Self, K, F>
where
K: Ord,
F: FnMut(&Self::Item) -> K,
{
SortBy {
phase: Phase::Source(self),
key,
descending: false,
_k: std::marker::PhantomData,
}
}
fn sort_by_desc<K, F>(self, key: F) -> SortBy<Self, K, F>
where
K: Ord,
F: FnMut(&Self::Item) -> K,
{
SortBy {
phase: Phase::Source(self),
key,
descending: true,
_k: std::marker::PhantomData,
}
}
fn avg(self) -> Option<f64>
where
Self::Item: Into<f64>,
{
let (mut sum, mut count) = (0f64, 0usize);
for item in self {
sum += item.into();
count += 1;
}
if count == 0 {
None
} else {
Some(sum / count as f64)
}
}
fn avg_by(self, mut value: impl FnMut(&Self::Item) -> f64) -> Option<f64> {
let (mut sum, mut count) = (0f64, 0usize);
for item in self {
sum += value(&item);
count += 1;
}
if count == 0 {
None
} else {
Some(sum / count as f64)
}
}
fn join(self, separator: &str) -> String
where
Self::Item: Display,
{
self.map(|item| item.to_string())
.collect::<Vec<String>>()
.join(separator)
}
fn join_by<S: Display>(
self,
mut value: impl FnMut(&Self::Item) -> S,
separator: &str,
) -> String {
self.map(|item| value(&item).to_string())
.collect::<Vec<String>>()
.join(separator)
}
fn has_exactly(self, n: usize) -> bool {
self.take(n.saturating_add(1)).count() == n
}
fn has_less_than(self, n: usize) -> bool {
self.take(n.saturating_add(1)).count() < n
}
fn has_less_or_exactly(self, n: usize) -> bool {
self.take(n.saturating_add(1)).count() <= n
}
fn has_more_than(self, n: usize) -> bool {
self.take(n.saturating_add(1)).count() > n
}
fn has_more_or_exactly(self, n: usize) -> bool {
self.take(n).count() >= n
}
fn reduce_and_map<A, R>(
self,
init: A,
mut fold: impl FnMut(A, Self::Item) -> A,
mut map: impl FnMut(A) -> R,
) -> R {
map(self.fold(init, &mut fold))
}
fn contains(mut self, item: &Self::Item) -> bool
where
Self::Item: PartialEq,
{
self.any(|x| &x == item)
}
}
impl<I: Iterator> FluentIter for I {}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::iter::FromIterator;
use std::rc::Rc;
struct Counting<I: Iterator> {
inner: I,
yielded: Rc<Cell<usize>>,
}
impl<I: Iterator> Iterator for Counting<I> {
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> {
let item = self.inner.next();
if item.is_some() {
self.yielded.set(self.yielded.get() + 1);
}
item
}
}
fn counting<I: Iterator>(inner: I) -> (Counting<I>, Rc<Cell<usize>>) {
let counter = Rc::new(Cell::new(0));
(
Counting {
inner,
yielded: Rc::clone(&counter),
},
counter,
)
}
#[test]
fn stable_group_order() {
let items = ["b", "a", "b", "c", "a"];
let grouped: Vec<_> = items.into_iter().group_by_stable(|x| *x).collect();
let keys: Vec<_> = grouped.iter().map(|(k, _)| *k).collect();
assert_eq!(keys, vec!["b", "a", "c"]);
assert_eq!(grouped[1].1, vec!["a", "a"]);
}
#[test]
fn combine_all_pairs_left_order() {
let left = [1, 2, 1];
let right = [(1, "x"), (1, "y"), (3, "z")];
let joined: Vec<_> = left
.iter()
.combine(right.iter(), |l| **l, |(k, _)| *k)
.collect();
assert_eq!(joined.len(), 4);
let shapes: Vec<(i32, &str)> = joined
.into_iter()
.map(|(l, (_, name))| (*l, *name))
.collect();
assert_eq!(shapes, vec![(1, "x"), (1, "y"), (1, "x"), (1, "y")]);
}
#[test]
fn combine_owned_items_fan_out_clones() {
let left = vec![String::from("a"), String::from("a")];
let right = vec![String::from("a1"), String::from("a2")];
let joined = left
.into_iter()
.combine(right, |l| l.clone(), |r| r[..1].to_owned());
let shapes: Vec<(String, String)> = joined.into_iter().collect();
assert_eq!(
shapes,
vec![
("a".to_owned(), "a1".to_owned()),
("a".to_owned(), "a2".to_owned()),
("a".to_owned(), "a1".to_owned()),
("a".to_owned(), "a2".to_owned()),
]
);
}
#[test]
fn distinct_keeps_first_per_key() {
let items = ["bb", "a1", "b1", "a2", "c"];
let out: Vec<_> = items
.into_iter()
.distinct_by(|s| s.chars().next().unwrap())
.collect();
assert_eq!(out, vec!["bb", "a1", "c"]);
}
#[test]
fn to_map_last_write_wins() {
let items = vec![("k", 1), ("j", 2), ("k", 3)];
let map = items.into_iter().to_map_by(|(k, _)| *k, |(_, v)| *v);
assert_eq!(map.get("k"), Some(&3));
assert_eq!(map.get("j"), Some(&2));
assert_eq!(map.len(), 2);
}
#[test]
fn distinct_is_lazy_and_short_circuits() {
let (source, counter) = counting([1, 1, 2, 3, 3, 3, 4].into_iter());
let out: Vec<i32> = source.distinct().take(2).collect();
assert_eq!(out, vec![1, 2]);
assert!(counter.get() <= 4, "consumed {}", counter.get());
}
#[test]
fn is_distinct_checks_and_short_circuits() {
assert!([1, 2, 3].into_iter().is_distinct());
assert!(std::iter::empty::<i32>().is_distinct());
let (source, counter) = counting([1, 2, 1, 2, 1].into_iter());
assert!(!source.is_distinct());
assert_eq!(counter.get(), 3);
}
#[test]
fn is_distinct_by_short_circuits() {
assert!([("a", 1), ("b", 2)].into_iter().is_distinct_by(|(k, _)| *k));
let (source, counter) = counting([("a", 1), ("b", 2), ("a", 3), ("b", 4)].into_iter());
assert!(!source.is_distinct_by(|(k, _)| *k));
assert_eq!(counter.get(), 3);
}
#[test]
fn when_empty_uses_fallback_only_for_empty_source() {
let empty: [i32; 0] = [];
let out: Vec<i32> = empty.into_iter().when_empty([7, 8]).collect();
assert_eq!(out, vec![7, 8]);
let (fallback_source, fallback_counter) = counting([7, 8].into_iter());
let out: Vec<i32> = [1, 2].into_iter().when_empty(fallback_source).collect();
assert_eq!(out, vec![1, 2]);
assert_eq!(fallback_counter.get(), 0, "fallback must stay untouched");
}
#[test]
fn when_empty_with_empty_fallback_is_empty() {
let empty: [i32; 0] = [];
let out: Vec<i32> = empty.into_iter().when_empty(empty).collect();
assert!(out.is_empty());
}
#[test]
fn when_empty_falls_back_after_late_first_value_absent() {
let (source, counter) = counting(std::iter::empty::<i32>());
let out: Vec<i32> = source.when_empty([9]).collect();
assert_eq!(out, vec![9]);
assert_eq!(counter.get(), 0);
}
#[test]
fn repeat_replays_whole_sequence() {
let out: Vec<i32> = [1, 2].into_iter().repeat(3).collect();
assert_eq!(out, vec![1, 2, 1, 2, 1, 2]);
let out: Vec<i32> = [1, 2].into_iter().repeat(1).collect();
assert_eq!(out, vec![1, 2]);
}
#[test]
fn repeat_zero_never_consumes_source() {
let (source, counter) = counting([1, 2, 3].into_iter());
let out: Vec<i32> = source.repeat(0).collect();
assert!(out.is_empty());
assert_eq!(counter.get(), 0);
}
#[test]
fn repeat_streams_first_pass_before_replaying() {
let (source, counter) = counting([1, 2, 3].into_iter());
let mut rep = source.repeat(2);
assert_eq!(rep.next(), Some(1));
assert_eq!(counter.get(), 1);
assert_eq!(rep.next(), Some(2));
assert_eq!(rep.next(), Some(3));
assert_eq!(rep.next(), Some(1)); assert_eq!(rep.count(), 2);
}
#[test]
fn repeat_of_empty_source_is_empty() {
let out: Vec<i32> = std::iter::empty::<i32>().repeat(4).collect();
assert!(out.is_empty());
}
#[test]
fn distinct_by_is_lazy_and_short_circuits() {
let (source, counter) = counting([1, 1, 2, 2, 3, 3, 4, 4].into_iter());
let out: Vec<i32> = source.distinct_by(|x| *x).take(2).collect();
assert_eq!(out, vec![1, 2]);
assert_eq!(counter.get(), 3); }
#[test]
fn buffered_adapters_consume_nothing_until_polled() {
let (source, counter) = counting([3, 1, 2].into_iter());
let _grouped = source.group_by_stable(|x| *x);
assert_eq!(counter.get(), 0);
let (source, counter) = counting([3, 1, 2].into_iter());
let mut sorted = source.sorted();
assert_eq!(counter.get(), 0);
assert_eq!(sorted.next(), Some(1)); assert_eq!(counter.get(), 3);
let (source, counter) = counting([vec![1], vec![2]].into_iter());
let _joined = source.combine_join(|x| *x);
assert_eq!(counter.get(), 0);
}
#[test]
fn combine_indexes_right_on_first_pull_and_streams_left() {
let (left, left_pulls) = counting([1, 2, 3, 1, 2, 3].into_iter());
let right = [(1, "x")];
let mut joined = left.combine(right, |l| *l, |(k, _)| *k);
assert_eq!(left_pulls.get(), 0);
assert_eq!(joined.next(), Some((1, (1, "x"))));
assert_eq!(left_pulls.get(), 1);
assert_eq!(joined.next(), Some((1, (1, "x"))));
assert_eq!(joined.next(), None);
}
#[test]
fn sort_family_is_stable() {
let rows = [("b", 1), ("a", 2), ("b", 3), ("a", 1)];
let by_value: Vec<_> = rows.into_iter().sort_by(|(_, v)| *v).collect();
assert_eq!(by_value, vec![("b", 1), ("a", 1), ("a", 2), ("b", 3)]);
let desc: Vec<_> = rows.into_iter().sort_by_desc(|(_, v)| *v).collect();
assert_eq!(desc, vec![("b", 3), ("a", 2), ("b", 1), ("a", 1)]);
let asc: Vec<u8> = [3u8, 1, 2].into_iter().sorted().collect();
assert_eq!(asc, vec![1, 2, 3]);
let custom: Vec<i32> = [1, 2, 3].into_iter().sort_with(|a, b| b.cmp(a)).collect();
assert_eq!(custom, vec![3, 2, 1]);
}
#[test]
fn avg_handles_ints_floats_and_empty() {
assert_eq!([2, 4, 6].into_iter().avg(), Some(4.0));
assert_eq!(std::iter::empty::<i32>().avg(), None);
let rows = [("x", 10.0), ("y", 20.0)];
assert_eq!(rows.into_iter().avg_by(|(_, v)| *v), Some(15.0));
assert_eq!(std::iter::empty::<&str>().avg_by(|_| 1.0), None);
}
#[test]
fn join_displays_and_projects() {
assert_eq!([1, 2, 3].into_iter().join(", "), "1, 2, 3");
assert_eq!(std::iter::empty::<i32>().join(","), "");
let out = [1, 2].into_iter().join_by(|v| format!("{v:>3}"), "-");
assert_eq!(out, " 1- 2");
}
#[test]
fn has_cardinality_matrix() {
let len = |n: usize| (0..n).collect::<Vec<_>>();
assert!(len(2).into_iter().has_exactly(2));
assert!(!len(3).into_iter().has_exactly(2));
assert!(!len(1).into_iter().has_exactly(2));
assert!(len(0).into_iter().has_exactly(0));
assert!(len(1).into_iter().has_less_than(2));
assert!(!len(2).into_iter().has_less_than(2));
assert!(len(0).into_iter().has_less_than(1));
assert!(!len(0).into_iter().has_less_than(0));
assert!(len(2).into_iter().has_less_or_exactly(2));
assert!(!len(3).into_iter().has_less_or_exactly(2));
assert!(len(3).into_iter().has_more_than(2));
assert!(!len(2).into_iter().has_more_than(2));
assert!(len(2).into_iter().has_more_or_exactly(2));
assert!(!len(1).into_iter().has_more_or_exactly(2));
assert!(len(5).into_iter().has_more_or_exactly(2));
}
#[test]
fn has_cardinality_short_circuits() {
let (source, counter) = counting(0..1_000_000i64);
assert!(source.has_more_than(2));
assert_eq!(counter.get(), 3);
let (source, counter) = counting(0..1_000_000i64);
assert!(!source.has_less_than(2));
assert_eq!(counter.get(), 3);
let (source, counter) = counting(0..1_000_000i64);
assert!(source.has_more_or_exactly(2));
assert_eq!(counter.get(), 2);
}
#[test]
fn reduce_and_map_folds_then_projects() {
let report =
[1, 2, 3]
.into_iter()
.reduce_and_map(0, |acc, x| acc + x, |sum| format!("total {sum}"));
assert_eq!(report, "total 6");
let count = std::iter::empty::<i32>().reduce_and_map(0, |a, _| a + 1, |c| c);
assert_eq!(count, 0);
}
#[test]
fn contains_compares_and_short_circuits() {
assert!(["a", "b"].into_iter().contains(&"b"));
assert!(!["a", "b"].into_iter().contains(&"c"));
let (source, counter) =
counting(vec![String::from("x"), String::from("hit"), String::from("y")].into_iter());
assert!(source.contains(&String::from("hit")));
assert_eq!(counter.get(), 2);
}
#[test]
fn to_map_chain_builds_nested_leaves() {
let rows = [
("sp", "sa", 1),
("sp", "rj", 2),
("sp", "sa", 3),
("mg", "sa", 4),
];
let index = rows
.into_iter()
.to_map_chain(|(a, _, _)| *a, |(_, b, _)| *b);
assert_eq!(index.len(), 2);
assert_eq!(
index["sp"]["sa"]
.iter()
.map(|(_, _, v)| *v)
.collect::<Vec<_>>(),
vec![1, 3]
);
assert_eq!(index["sp"]["rj"].len(), 1);
assert_eq!(index["mg"]["sa"].len(), 1);
}
#[test]
fn to_map_chain_reduce_folds_leaves() {
let rows = [("sp", "sa", 1), ("sp", "sa", 2), ("sp", "rj", 5)];
let totals = rows.into_iter().to_map_chain_reduce(
|(a, _, _)| *a,
|(_, b, _)| *b,
0,
|acc, (_, _, v)| acc + v,
);
assert_eq!(totals["sp"]["sa"], 3);
assert_eq!(totals["sp"]["rj"], 5);
assert!(!totals.contains_key("mg"));
}
#[test]
fn combine_join_folds_iterables_by_key() {
let a = [(1, "a1"), (2, "a2")];
let b = [(1, "b1"), (2, "b2")];
let c = [(1, "c1")];
let rows: Vec<Vec<_>> = [a.as_slice(), b.as_slice(), c.as_slice()]
.into_iter()
.combine_join(|(k, _)| *k)
.collect();
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0].iter().map(|(_, v)| *v).collect::<Vec<_>>(),
vec!["a1", "b1", "c1"]
);
}
#[test]
fn combine_join_drops_keys_missing_from_any_iterable() {
let a = [(1, "a1"), (2, "a2")];
let b = [(1, "b1")];
let c = [(2, "c2")];
let rows: Vec<Vec<_>> = [a.as_slice(), b.as_slice(), c.as_slice()]
.into_iter()
.combine_join(|(k, _)| *k)
.collect();
assert!(rows.is_empty());
}
#[test]
fn combine_join_multiplies_duplicate_keys() {
let a = [(1, "a1"), (1, "a2")];
let b = [(1, "b1")];
let rows: Vec<Vec<_>> = [a.as_slice(), b.as_slice()]
.into_iter()
.combine_join(|(k, _)| *k)
.collect();
assert_eq!(rows.len(), 2);
assert_eq!(
HashSet::from_iter(rows.iter().map(|r| r[0].1)),
HashSet::from(["a1", "a2"])
);
}
#[test]
fn adapters_resolve_std_iterator_methods() {
let rows = [("b", 1), ("a", 2), ("b", 3)];
assert_eq!(rows.iter().sort_by(|(_, v)| *v).count(), 3);
assert_eq!(
rows.iter().sort_by_desc(|(_, v)| *v).next().copied(),
Some(("b", 3))
);
assert_eq!(
rows.iter().sort_by(|(_, v)| *v).next().copied(),
Some(("b", 1))
);
let groups = vec![vec![("x", 1)], vec![("x", 2), ("y", 3)]];
assert_eq!(groups.into_iter().combine_join(|(k, _)| *k).count(), 1);
}
#[test]
fn combine_join_of_nothing_is_empty() {
let rows: Vec<Vec<_>> = std::iter::empty::<&[(i32, &str)]>()
.combine_join(|(k, _)| *k)
.collect();
assert!(rows.is_empty());
}
#[test]
fn chaining_matches_readme_example() {
let rows = [("sa", 10), ("sp", 30), ("sa", 25)];
let by_state: Vec<_> = rows.into_iter().group_by_stable(|(s, _)| *s).collect();
assert_eq!(by_state[0].1, vec![("sa", 10), ("sa", 25)]);
let report = rows
.into_iter()
.map(|(_, v)| v)
.join_by(|v| format!("{v:>4}"), " | ");
assert_eq!(report, " 10 | 30 | 25");
}
}