From 2844693c0c244fda3a6c4c09ad92ee453c9a298a Mon Sep 17 00:00:00 2001 From: Martin Ockajak Date: Wed, 10 Jul 2024 23:19:36 +0200 Subject: [PATCH] . --- README.md | 8 +- src/extensions/collectible.rs | 37 ++-- src/extensions/collections/binary_heap.rs | 10 ++ src/extensions/collections/btree_map.rs | 10 ++ src/extensions/collections/btree_set.rs | 10 ++ src/extensions/collections/hash_map.rs | 10 ++ src/extensions/collections/hash_set.rs | 10 ++ src/extensions/collections/linked_list.rs | 10 ++ src/extensions/collections/slice.rs | 10 ++ src/extensions/collections/vec.rs | 10 ++ src/extensions/collections/vec_deque.rs | 10 ++ src/extensions/map.rs | 171 +++++++++++++++--- src/extensions/traversable.rs | 205 +++++++++++++++++++--- src/lib.rs | 8 +- tests/extensions/collectible.rs | 2 +- 15 files changed, 451 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 7cee8b1..23fe7c9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ use cantrip::*; let data = vec![1, 2, 3]; -data.fold(0, |r, x| r + x); // 6 +data.fold_to(0, |r, x| r + x); // 6 data.filter(|&x| x > 1); // vec![2, 3] @@ -81,7 +81,8 @@ data.group_by(|x| x % 2); // HashMap::from([(0, vec![2]), (1, vec![1, 3] | *flat_map* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | | *flat_map_to* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | | *flat* | :heavy_check_mark: | | :heavy_check_mark: | | -| *fold* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | +| *fold* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| *fold_to* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | | *frequencies* | :heavy_check_mark: | | | | | *frequencies_by* | :heavy_check_mark: | | | | | *group_by* | :heavy_check_mark: | | :heavy_check_mark: | | @@ -129,7 +130,8 @@ data.group_by(|x| x % 2); // HashMap::from([(0, vec![2]), (1, vec![1, 3] | *product* | :heavy_check_mark: | | :heavy_check_mark: | | | *product_keys* | | | | :heavy_check_mark: | | *product_values* | | | | :heavy_check_mark: | -| *reduce* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | +| *reduce* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| *reduce_to* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | | *replace* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | | *replace_all* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | | *replace_at* | :heavy_check_mark: | | | | diff --git a/src/extensions/collectible.rs b/src/extensions/collectible.rs index 173f415..be7d2ad 100644 --- a/src/extensions/collectible.rs +++ b/src/extensions/collectible.rs @@ -561,14 +561,14 @@ pub trait Collectible: IntoIterator { /// Folds every element into an accumulator by applying an operation, /// returning the final result. /// - /// `fold()` takes two arguments: an initial value, and a closure with two + /// `fold_to()` takes two arguments: an initial value, and a closure with two /// arguments: an 'accumulator', and an element. The closure returns the value that /// the accumulator should have for the next iteration. /// /// The initial value is the value the accumulator will have on the first /// call. /// - /// After applying this closure to every element of the collection, `fold()` + /// After applying this closure to every element of the collection, `fold_to()` /// returns the accumulator. /// /// This operation is sometimes called 'reduce' or 'inject'. @@ -576,13 +576,17 @@ pub trait Collectible: IntoIterator { /// Folding is useful whenever you have a collection of something, and want /// to produce a single value from it. /// + /// This is a consuming variant of [`fold`]. + /// /// Note: [`reduce()`] can be used to use the first element as the initial /// value, if the accumulator type and item type is the same. /// - /// Note: `fold()` combines elements in a *left-associative* fashion. For associative + /// Note: `fold_to()` combines elements in a *left-associative* fashion. For associative /// operators like `+`, the order the elements are combined in is not important, but for non-associative /// operators like `-` the order will affect the final result. - /// For a *right-associative* version of `fold()`, see [`rfold()`]. + /// For a *right-associative* version of `fold_to()`, see [`rfold_to()`]. + /// + /// [`fold`]: crate::Traversable::fold /// /// # Examples /// @@ -594,7 +598,7 @@ pub trait Collectible: IntoIterator { /// let a = vec![1, 2, 3]; /// /// // the sum of all the elements of the array - /// let sum = a.fold(0, |acc, x| acc + x); + /// let sum = a.fold_to(0, |acc, x| acc + x); /// /// assert_eq!(sum, 6); /// ``` @@ -610,7 +614,7 @@ pub trait Collectible: IntoIterator { /// /// And so, our final result, `6`. /// - /// This example demonstrates the left-associative nature of `fold()`: + /// This example demonstrates the left-associative nature of `fold_to()`: /// it builds a string, starting with an initial value /// and continuing with each element from the front until the back: /// @@ -621,7 +625,7 @@ pub trait Collectible: IntoIterator { /// /// let zero = "0".to_string(); /// - /// let result = numbers.fold(zero, |acc, x| { + /// let result = numbers.fold_to(zero, |acc, x| { /// format!("({acc} + {x})") /// }); /// @@ -629,7 +633,7 @@ pub trait Collectible: IntoIterator { /// ``` /// It's common for people who haven't used collections a lot to /// use a `for` loop with a list of things to build up a result. Those - /// can be turned into `fold()`s: + /// can be turned into `fold_to()`s: /// /// [`for`]: ../../book/ch03-05-control-flow.html#looping-through-a-collection-with-for /// @@ -646,13 +650,13 @@ pub trait Collectible: IntoIterator { /// } /// /// // fold: - /// let result2 = numbers.fold(0, |acc, x| acc + x); + /// let result2 = numbers.fold_to(0, |acc, x| acc + x); /// /// // they're the same /// assert_eq!(result, result2); /// ``` #[inline] - fn fold(self, initial_value: B, function: impl FnMut(B, Item) -> B) -> B + fn fold_to(self, initial_value: B, function: impl FnMut(B, Item) -> B) -> B where Self: Sized, { @@ -1084,11 +1088,14 @@ pub trait Collectible: IntoIterator { /// result of the reduction. /// /// The reducing function is a closure with two arguments: an 'accumulator', and an element. - /// For collections with at least one element, this is the same as [`fold()`] + /// For collections with at least one element, this is the same as [`fold_to()`] /// with the first element of the collection as the initial accumulator value, folding /// every subsequent element into it. /// - /// [`fold()`]: crate::Traversable::fold + /// This is a consuming variant of [`reduce`]. + /// + /// [`fold_to()`]: Collectible::fold_to + /// [`reduce()`]: crate::Traversable::reduce /// /// # Example /// @@ -1098,18 +1105,18 @@ pub trait Collectible: IntoIterator { /// # let source = vec![1, 2, 3]; /// let a = vec![1, 2, 3]; /// - /// let reduced = a.reduce(|acc, e| acc + e).unwrap(); + /// let reduced = a.reduce_to(|acc, e| acc + e).unwrap(); /// /// assert_eq!(reduced, 6); /// /// // Which is equivalent to doing it with `fold`: /// # let a = source.clone(); - /// let folded = a.fold(0, |acc, e| acc + e); + /// let folded = a.fold_to(0, |acc, e| acc + e); /// /// assert_eq!(reduced, folded); /// ``` #[inline] - fn reduce(self, function: impl FnMut(Item, Item) -> Item) -> Option + fn reduce_to(self, function: impl FnMut(Item, Item) -> Item) -> Option where Self: Sized, { diff --git a/src/extensions/collections/binary_heap.rs b/src/extensions/collections/binary_heap.rs index 8bad957..e13f74e 100644 --- a/src/extensions/collections/binary_heap.rs +++ b/src/extensions/collections/binary_heap.rs @@ -29,6 +29,11 @@ impl Traversable for BinaryHeap { self.iter().find_map(function) } + #[inline] + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B { + fold(self.iter(), initial_value, function) + } + #[inline] fn max_by(&self, mut compare: impl FnMut(&Item, &Item) -> Ordering) -> Option<&Item> { self.iter().max_by(|&x, &y| compare(x, y)) @@ -59,6 +64,11 @@ impl Traversable for BinaryHeap { minmax_by_key(self.iter(), to_key) } + #[inline] + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option { + reduce(self.iter(), function) + } + #[inline] fn subset<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> bool where diff --git a/src/extensions/collections/btree_map.rs b/src/extensions/collections/btree_map.rs index 511ffb5..4401f3e 100644 --- a/src/extensions/collections/btree_map.rs +++ b/src/extensions/collections/btree_map.rs @@ -65,6 +65,11 @@ impl Map for BTreeMap { flat_map_pairs(self.iter(), |&x| function(x)) } + #[inline] + fn fold(self, initial_value: B, function: impl FnMut(B, (&Key, &Value)) -> B) -> B { + fold_pairs(self.iter(), initial_value, function) + } + #[inline] fn map(&self, mut function: impl FnMut((&Key, &Value)) -> (L, W)) -> Self::This where @@ -116,6 +121,11 @@ impl Map for BTreeMap { partition_map_pairs(self.iter(), function) } + #[inline] + fn reduce(self, function: impl FnMut((&Key, &Value), (&Key, &Value)) -> (Key, Value)) -> Option<(Key, Value)> { + reduce_pairs(self.iterator(), function) + } + #[inline] fn scan( self, initial_state: S, function: impl FnMut(&mut S, (&Key, &Value)) -> Option<(L, W)>, diff --git a/src/extensions/collections/btree_set.rs b/src/extensions/collections/btree_set.rs index 41d72f6..81f8aa2 100644 --- a/src/extensions/collections/btree_set.rs +++ b/src/extensions/collections/btree_set.rs @@ -29,6 +29,11 @@ impl Traversable for BTreeSet { self.iter().find_map(function) } + #[inline] + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B { + fold(self.iter(), initial_value, function) + } + #[inline] fn max_by(&self, mut compare: impl FnMut(&Item, &Item) -> Ordering) -> Option<&Item> { self.iter().max_by(|&x, &y| compare(x, y)) @@ -59,6 +64,11 @@ impl Traversable for BTreeSet { minmax_by_key(self.iter(), to_key) } + #[inline] + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option { + reduce(self.iter(), function) + } + #[inline] fn subset<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> bool where diff --git a/src/extensions/collections/hash_map.rs b/src/extensions/collections/hash_map.rs index 5317ef8..68329c8 100644 --- a/src/extensions/collections/hash_map.rs +++ b/src/extensions/collections/hash_map.rs @@ -64,6 +64,11 @@ impl Map for HashMap { flat_map_pairs(self.iter(), |&x| function(x)) } + #[inline] + fn fold(self, initial_value: B, function: impl FnMut(B, (&Key, &Value)) -> B) -> B { + fold_pairs(self.iter(), initial_value, function) + } + #[inline] fn map(&self, mut function: impl FnMut((&Key, &Value)) -> (L, W)) -> Self::This where @@ -115,6 +120,11 @@ impl Map for HashMap { partition_map_pairs(self.iter(), function) } + #[inline] + fn reduce(self, function: impl FnMut((&Key, &Value), (&Key, &Value)) -> (Key, Value)) -> Option<(Key, Value)> { + reduce_pairs(self.iterator(), function) + } + #[inline] fn scan( self, initial_state: S, function: impl FnMut(&mut S, (&Key, &Value)) -> Option<(L, W)>, diff --git a/src/extensions/collections/hash_set.rs b/src/extensions/collections/hash_set.rs index f76925a..26046c2 100644 --- a/src/extensions/collections/hash_set.rs +++ b/src/extensions/collections/hash_set.rs @@ -30,6 +30,11 @@ impl Traversable for HashSet { self.iter().find_map(function) } + #[inline] + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B { + fold(self.iter(), initial_value, function) + } + #[inline] fn max_by(&self, mut compare: impl FnMut(&Item, &Item) -> Ordering) -> Option<&Item> { self.iter().max_by(|&x, &y| compare(x, y)) @@ -60,6 +65,11 @@ impl Traversable for HashSet { minmax_by_key(self.iter(), to_key) } + #[inline] + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option { + reduce(self.iter(), function) + } + #[inline] fn subset<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> bool where diff --git a/src/extensions/collections/linked_list.rs b/src/extensions/collections/linked_list.rs index 33b4520..91c564c 100644 --- a/src/extensions/collections/linked_list.rs +++ b/src/extensions/collections/linked_list.rs @@ -31,6 +31,11 @@ impl Traversable for LinkedList { self.iter().find_map(function) } + #[inline] + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B { + fold(self.iter(), initial_value, function) + } + #[inline] fn max_by(&self, mut compare: impl FnMut(&Item, &Item) -> Ordering) -> Option<&Item> { self.iter().max_by(|&x, &y| compare(x, y)) @@ -61,6 +66,11 @@ impl Traversable for LinkedList { minmax_by_key(self.iter(), to_key) } + #[inline] + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option { + reduce(self.iter(), function) + } + #[inline] fn subset<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> bool where diff --git a/src/extensions/collections/slice.rs b/src/extensions/collections/slice.rs index 68629e5..ff50ae5 100644 --- a/src/extensions/collections/slice.rs +++ b/src/extensions/collections/slice.rs @@ -31,6 +31,11 @@ impl Traversable for [Item] { self.iter().find_map(function) } + #[inline] + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B { + fold(self.iter(), initial_value, function) + } + #[inline] fn max_by(&self, mut compare: impl FnMut(&Item, &Item) -> Ordering) -> Option<&Item> { self.iter().max_by(|&x, &y| compare(x, y)) @@ -61,6 +66,11 @@ impl Traversable for [Item] { minmax_by_key(self.iter(), to_key) } + #[inline] + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option { + reduce(self.iter(), function) + } + #[inline] fn subset<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> bool where diff --git a/src/extensions/collections/vec.rs b/src/extensions/collections/vec.rs index d2b96cb..96c74d3 100644 --- a/src/extensions/collections/vec.rs +++ b/src/extensions/collections/vec.rs @@ -30,6 +30,11 @@ impl Traversable for Vec { self.iter().find_map(function) } + #[inline] + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B { + fold(self.iter(), initial_value, function) + } + #[inline] fn max_by(&self, mut compare: impl FnMut(&Item, &Item) -> Ordering) -> Option<&Item> { self.iter().max_by(|&x, &y| compare(x, y)) @@ -60,6 +65,11 @@ impl Traversable for Vec { minmax_by_key(self.iter(), to_key) } + #[inline] + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option { + reduce(self.iter(), function) + } + #[inline] fn subset<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> bool where diff --git a/src/extensions/collections/vec_deque.rs b/src/extensions/collections/vec_deque.rs index 98994ac..7e156c5 100644 --- a/src/extensions/collections/vec_deque.rs +++ b/src/extensions/collections/vec_deque.rs @@ -31,6 +31,11 @@ impl Traversable for VecDeque { self.iter().find_map(function) } + #[inline] + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B { + fold(self.iter(), initial_value, function) + } + #[inline] fn max_by(&self, mut compare: impl FnMut(&Item, &Item) -> Ordering) -> Option<&Item> { self.iter().max_by(|&x, &y| compare(x, y)) @@ -61,6 +66,11 @@ impl Traversable for VecDeque { minmax_by_key(self.iter(), to_key) } + #[inline] + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option { + reduce(self.iter(), function) + } + #[inline] fn subset<'a>(&'a self, elements: &'a impl Iterable = &'a Item>) -> bool where diff --git a/src/extensions/map.rs b/src/extensions/map.rs index b2ede17..0a8e903 100644 --- a/src/extensions/map.rs +++ b/src/extensions/map.rs @@ -1,12 +1,13 @@ #![allow(missing_docs)] -use crate::extensions::iterable::Iterable; use std::cmp::Ordering; use std::collections::HashSet; use std::hash::Hash; use std::iter; use std::iter::{Product, Sum}; +use crate::extensions::iterable::Iterable; + /// Map operations. /// /// Methods have the following properties: @@ -696,7 +697,7 @@ pub trait Map { /// This is a non-consuming variant of [`flat_map_to`]. /// /// [`map`]: Map::map - /// [`flat`]: crate::extensions::collectible::Collectible::flat + /// [`flat`]: crate::Collectible::flat /// [`flat_map_to`]: Map::flat_map_to /// /// # Example @@ -746,7 +747,7 @@ pub trait Map { /// This is a consuming variant of [`flat_map`]. /// /// [`map`]: Map::map - /// [`flat`]: crate::extensions::collectible::Collectible + /// [`flat`]: crate::Collectible::flat /// [`flat_map`]: Map::flat_map /// /// # Example @@ -801,13 +802,77 @@ pub trait Map { /// Folding is useful whenever you have a map of something, and want /// to produce a single value from it. /// + /// This is a non-consuming variant of [`fold_to`]. + /// /// Note: [`reduce()`] can be used to use the first entry as the initial /// value, if the accumulator type and item type is the same. /// - /// Note: `fold()` combines entrys in a *left-associative* fashion. For associative - /// operators like `+`, the order the entrys are combined in is not important, but for non-associative + /// Note: `fold()` combines entries in a *left-associative* fashion. For associative + /// operators like `+`, the order the entries are combined in is not important, but for non-associative + /// operators like `-` the order will affect the final result. + /// + /// [`fold_to`]: Map::fold_to + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use cantrip::*; + /// use std::collections::HashMap; + /// + /// let a = HashMap::from([ + /// (1, "a"), + /// (2, "b"), + /// (3, "c"), + /// ]); + /// + /// // the sum of all the elements of the array + /// let sum = a.fold(0, |acc, (&k, &v)| acc + k + v.len()); + /// + /// assert_eq!(sum, 9); + /// ``` + /// + /// Let's walk through each step of the iteration here: + /// + /// | element | acc | k | k | result | + /// |---------|-----|---|---|--------| + /// | | 0 | | | | + /// | 1 | 0 | 1 | a | 2 | + /// | 2 | 2 | 2 | b | 5 | + /// | 3 | 5 | 3 | c | 9 | + /// + /// And so, our final result, `9`. + fn fold(self, initial_value: B, function: impl FnMut(B, (&Key, &Value)) -> B) -> B; + + /// Folds every entry into an accumulator by applying an operation, + /// returning the final result. + /// + /// `fold_to()` takes two arguments: an initial value, and a closure with two + /// arguments: an 'accumulator', and an entry. The closure returns the value that + /// the accumulator should have for the next iteration. + /// + /// The initial value is the value the accumulator will have on the first + /// call. + /// + /// After applying this closure to every entry of the map, `fold_to()` + /// returns the accumulator. + /// + /// This operation is sometimes called 'reduce' or 'inject'. + /// + /// Folding is useful whenever you have a map of something, and want + /// to produce a single value from it. + /// + /// This is a consuming variant of [`fold`]. + /// + /// Note: [`reduce_to()`] can be used to use the first entry as the initial + /// value, if the accumulator type and item type is the same. + /// + /// Note: `fold_to()` combines entries in a *left-associative* fashion. For associative + /// operators like `+`, the order the entries are combined in is not important, but for non-associative /// operators like `-` the order will affect the final result. - /// For a *right-associative* version of `fold()`, see [`rfold()`]. + /// + /// [`fold`]: Map::fold /// /// # Examples /// @@ -824,7 +889,7 @@ pub trait Map { /// ]); /// /// // the sum of all the elements of the array - /// let sum = a.fold(0, |acc, (k, v)| acc + k + v.len()); + /// let sum = a.fold_to(0, |acc, (k, v)| acc + k + v.len()); /// /// assert_eq!(sum, 9); /// ``` @@ -839,11 +904,11 @@ pub trait Map { /// | 3 | 5 | 3 | c | 9 | /// /// And so, our final result, `9`. - fn fold(self, init: B, function: impl FnMut(B, (Key, Value)) -> B) -> B + fn fold_to(self, initial_value: B, function: impl FnMut(B, (Key, Value)) -> B) -> B where Self: IntoIterator + Sized, { - self.into_iter().fold(init, function) + self.into_iter().fold(initial_value, function) } /// Creates a map by retaining the values representing the intersection @@ -896,7 +961,7 @@ pub trait Map { /// `(Key, Value)` and returns a value of type `(L, W)`. /// The resulting other are collected into a new map of the same type. /// - /// This is a non-consuming variant of [`map_to`]. + /// This is a consuming variant of [`map_to`]. /// /// [`map_to`]: Map::map_to /// @@ -1095,7 +1160,7 @@ pub trait Map { /// Returns the entry that gives the maximum value with respect to the /// specified comparison function. /// - /// If several entrys are equally maximum, the last entry is + /// If several entries are equally maximum, the last entry is /// returned. If the map is empty, [`None`] is returned. /// /// # Example @@ -1120,7 +1185,7 @@ pub trait Map { /// Returns the entry that gives the maximum value from the /// specified function. /// - /// If several entrys are equally maximum, the last entry is + /// If several entries are equally maximum, the last entry is /// returned. If the map is empty, [`None`] is returned. /// /// # Example @@ -1144,7 +1209,7 @@ pub trait Map { /// Returns the maximum entry of a map. /// - /// If several entrys are equally maximum, the last entry is + /// If several entries are equally maximum, the last entry is /// returned. If the map is empty, [`None`] is returned. /// /// # Example @@ -1176,7 +1241,7 @@ pub trait Map { /// Returns the entry that gives the minimum value with respect to the /// specified comparison function. /// - /// If several entrys are equally minimum, the last entry is + /// If several entries are equally minimum, the last entry is /// returned. If the map is empty, [`None`] is returned. /// /// # Example @@ -1201,7 +1266,7 @@ pub trait Map { /// Returns the entry that gives the minimum value from the /// specified function. /// - /// If several entrys are equally minimum, the last entry is + /// If several entries are equally minimum, the last entry is /// returned. If the map is empty, [`None`] is returned. /// /// # Example @@ -1225,7 +1290,7 @@ pub trait Map { /// Returns the minimum entry of a map. /// - /// If several entrys are equally minimum, the last entry is + /// If several entries are equally minimum, the last entry is /// returned. If the map is empty, [`None`] is returned. /// /// # Example @@ -1343,8 +1408,8 @@ pub trait Map { /// specified predicate. /// /// The predicate passed to `partition()` can return `true`, or `false`. - /// `partition()` returns a pair, all the entrys for which it returned - /// `true`, and all the entrys for which it returned `false`. + /// `partition()` returns a pair, all the entries for which it returned + /// `true`, and all the entries for which it returned `false`. /// /// # Example /// @@ -1561,7 +1626,10 @@ pub trait Map { /// with the first element of the collection as the initial accumulator value, folding /// every subsequent element into it. /// + /// This is a non-consuming variant of [`reduce_to`]. + /// /// [`fold()`]: Map::fold + /// [`reduce_to()`]: Map::reduce_to /// /// # Example /// @@ -1580,17 +1648,62 @@ pub trait Map { /// (3, 4), /// ]); /// - /// let reduced = a.reduce(|(a, b), (k, v)| (a + k, b + v)).unwrap(); + /// let reduced = a.reduce(|(&a, &b), (&k, &v)| (a + k, b + v)).unwrap(); /// /// assert_eq!(reduced, (6, 9)); /// /// // Which is equivalent to doing it with `fold`: /// # let a = source.clone(); - /// let folded = a.fold((0, 0), |(a, b), (k, v)| (a + k, b + v)); + /// let folded = a.fold((0, 0), |(a, b), (&k, &v)| (a + k, b + v)); /// /// assert_eq!(reduced, folded); /// ``` - fn reduce(self, mut function: impl FnMut((Key, Value), (Key, Value)) -> (Key, Value)) -> Option<(Key, Value)> + fn reduce(self, function: impl FnMut((&Key, &Value), (&Key, &Value)) -> (Key, Value)) -> Option<(Key, Value)>; + + /// Reduces the elements to a single one, by repeatedly applying a reducing + /// operation. + /// + /// If the collection is empty, returns [`None`]; otherwise, returns the + /// result of the reduction. + /// + /// The reducing function is a closure with two arguments: an 'accumulator', and an element. + /// For collections with at least one element, this is the same as [`fold_to()`] + /// with the first element of the collection as the initial accumulator value, folding + /// every subsequent element into it. + /// + /// This is a consuming variant of [`reduce`]. + /// + /// [`fold_to()`]: Map::fold_to + /// [`reduce()`]: Map::reduce + /// + /// # Example + /// + /// ``` + /// use cantrip::*; + /// use std::collections::HashMap; + /// + /// # let source = HashMap::from([ + /// # (1, 2), + /// # (2, 3), + /// # (3, 4), + /// # ]); + /// let a = HashMap::from([ + /// (1, 2), + /// (2, 3), + /// (3, 4), + /// ]); + /// + /// let reduced = a.reduce_to(|(a, b), (k, v)| (a + k, b + v)).unwrap(); + /// + /// assert_eq!(reduced, (6, 9)); + /// + /// // Which is equivalent to doing it with `fold`: + /// # let a = source.clone(); + /// let folded = a.fold_to((0, 0), |(a, b), (k, v)| (a + k, b + v)); + /// + /// assert_eq!(reduced, folded); + /// ``` + fn reduce_to(self, mut function: impl FnMut((Key, Value), (Key, Value)) -> (Key, Value)) -> Option<(Key, Value)> where Self: IntoIterator + Sized, { @@ -1882,6 +1995,13 @@ pub(crate) fn find_map_pairs<'a, K: 'a, V: 'a, B>( iterator.find_map(|x| function(&x)) } +#[inline] +pub(crate) fn fold_pairs<'a, K: 'a, V: 'a, B>( + iterator: impl Iterator, init: B, function: impl FnMut(B, (&K, &V)) -> B, +) -> B { + iterator.fold(init, function) +} + #[inline] pub(crate) fn flat_map_pairs<'a, K: 'a, V: 'a, L, W, R: IntoIterator, Result: FromIterator<(L, W)>>( iterator: impl Iterator, mut function: impl FnMut(&(&K, &V)) -> R, @@ -1943,3 +2063,12 @@ where } (result_left, result_right) } + +#[inline] +pub(crate) fn reduce_pairs<'a, K: 'a, V: 'a>( + mut iterator: impl Iterator, mut function: impl FnMut((&K, &V), (&K, &V)) -> (K, V), +) -> Option<(K, V)> { + iterator.next().and_then(|value1| { + iterator.next().map(|value2| iterator.fold(function(value1, value2), |r, x| function((&r.0, &r.1), x))) + }) +} diff --git a/src/extensions/traversable.rs b/src/extensions/traversable.rs index dacab90..d252068 100644 --- a/src/extensions/traversable.rs +++ b/src/extensions/traversable.rs @@ -138,6 +138,105 @@ pub trait Traversable { /// ``` fn find_map(&self, function: impl FnMut(&Item) -> Option) -> Option; + /// Folds every element into an accumulator by applying an operation, + /// returning the final result. + /// + /// `fold()` takes two arguments: an initial value, and a closure with two + /// arguments: an 'accumulator', and an element. The closure returns the value that + /// the accumulator should have for the next iteration. + /// + /// The initial value is the value the accumulator will have on the first + /// call. + /// + /// After applying this closure to every element of the collection, `fold()` + /// returns the accumulator. + /// + /// This operation is sometimes called 'reduce' or 'inject'. + /// + /// Folding is useful whenever you have a collection of something, and want + /// to produce a single value from it. + /// + /// This is a non-consuming variant of [`fold_to`]. + /// + /// Note: [`reduce()`] can be used to use the first element as the initial + /// value, if the accumulator type and item type is the same. + /// + /// Note: `fold()` combines elements in a *left-associative* fashion. For associative + /// operators like `+`, the order the elements are combined in is not important, but for non-associative + /// operators like `-` the order will affect the final result. + /// For a *right-associative* version of `fold()`, see [`rfold()`]. + /// + /// [`fold_to`]: crate::Collectible::fold_to + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use cantrip::*; + /// + /// let a = vec![1, 2, 3]; + /// + /// // the sum of all the elements of the array + /// let sum = a.fold(0, |acc, x| acc + x); + /// + /// assert_eq!(sum, 6); + /// ``` + /// + /// Let's walk through each step of the iteration here: + /// + /// | element | acc | x | result | + /// |---------|-----|---|--------| + /// | | 0 | | | + /// | 1 | 0 | 1 | 1 | + /// | 2 | 1 | 2 | 3 | + /// | 3 | 3 | 3 | 6 | + /// + /// And so, our final result, `6`. + /// + /// This example demonstrates the left-associative nature of `fold()`: + /// it builds a string, starting with an initial value + /// and continuing with each element from the front until the back: + /// + /// ``` + /// use cantrip::*; + /// + /// let numbers = vec![1, 2, 3, 4, 5]; + /// + /// let zero = "0".to_string(); + /// + /// let result = numbers.fold(zero, |acc, &x| { + /// format!("({acc} + {x})") + /// }); + /// + /// assert_eq!(result, "(((((0 + 1) + 2) + 3) + 4) + 5)"); + /// ``` + /// It's common for people who haven't used collections a lot to + /// use a `for` loop with a list of things to build up a result. Those + /// can be turned into `fold()`s: + /// + /// [`for`]: ../../book/ch03-05-control-flow.html#looping-through-a-collection-with-for + /// + /// ``` + /// use cantrip::*; + /// + /// let numbers = vec![1, 2, 3, 4, 5]; + /// + /// let mut result = 0; + /// + /// // for loop: + /// for i in &numbers { + /// result = result + i; + /// } + /// + /// // fold: + /// let result2 = numbers.fold(0, |acc, &x| acc + x); + /// + /// // they're the same + /// assert_eq!(result, result2); + /// ``` + fn fold(&self, initial_value: B, function: impl FnMut(B, &Item) -> B) -> B; + /// Returns the element that gives the maximum value with respect to the /// specified comparison function. /// @@ -190,7 +289,7 @@ pub trait Traversable { /// /// assert_eq!( /// vec![2.4, f32::NAN, 1.3] - /// .reduce(f32::max) + /// .reduce_to(f32::max) /// .unwrap(), /// 2.4 /// ); @@ -269,7 +368,7 @@ pub trait Traversable { /// /// assert_eq!( /// vec![2.4, f32::NAN, 1.3] - /// .reduce(f32::min) + /// .reduce_to(f32::min) /// .unwrap(), /// 1.3 /// ); @@ -360,6 +459,42 @@ pub trait Traversable { self.minmax_by(Ord::cmp) } + /// Reduces the elements to a single one, by repeatedly applying a reducing + /// operation. + /// + /// If the collection is empty, returns [`None`]; otherwise, returns the + /// result of the reduction. + /// + /// The reducing function is a closure with two arguments: an 'accumulator', and an element. + /// For collections with at least one element, this is the same as [`fold()`] + /// with the first element of the collection as the initial accumulator value, folding + /// every subsequent element into it. + /// + /// This is a non-consuming variant of [`reduce_to`]. + /// + /// [`fold()`]: Traversable::fold + /// [`reduce_to()`]: crate::Collectible::reduce + /// + /// # Example + /// + /// ``` + /// use crate::cantrip::*; + /// + /// # let source = vec![1, 2, 3]; + /// let a = vec![1, 2, 3]; + /// + /// let reduced = a.reduce(|&acc, &e| acc + e).unwrap(); + /// + /// assert_eq!(reduced, 6); + /// + /// // Which is equivalent to doing it with `fold`: + /// # let a = source.clone(); + /// let folded = a.fold(0, |acc, &e| acc + e); + /// + /// assert_eq!(reduced, folded); + /// ``` + fn reduce(&self, function: impl FnMut(&Item, &Item) -> Item) -> Option; + /// Tests if all the elements of a collection can be found in another collection. /// /// Returns `true` if this collection is empty. @@ -441,6 +576,41 @@ pub(crate) fn count_by<'a, Item: 'a>( iterator.filter(|&x| predicate(x)).count() } +#[inline] +pub(crate) fn fold<'a, Item: 'a, B>( + iterator: impl Iterator, init: B, function: impl FnMut(B, &Item) -> B, +) -> B { + iterator.fold(init, function) +} + +pub(crate) fn minmax_by<'a, Item: 'a>( + mut iterator: impl Iterator, mut compare: impl FnMut(&Item, &Item) -> Ordering, +) -> Option<(&'a Item, &'a Item)> { + match iterator.next() { + Some(item) => { + let mut min = item; + let mut max = min; + for item in iterator { + if compare(item, min) == Ordering::Less { + min = item; + } + if compare(item, max) != Ordering::Less { + max = item; + } + } + Some((min, max)) + } + None => None, + } +} + +#[inline] +pub(crate) fn minmax_by_key<'a, Item: 'a, K: Ord>( + iterator: impl Iterator, mut to_key: impl FnMut(&Item) -> K, +) -> Option<(&'a Item, &'a Item)> { + minmax_by(iterator, |x, y| to_key(x).cmp(&to_key(y))) +} + pub(crate) fn subset<'a, Item>( iterator: impl Iterator, elements: &'a impl Iterable = &'a Item>, ) -> bool @@ -473,30 +643,11 @@ where true } -pub(crate) fn minmax_by<'a, Item: 'a>( - mut iterator: impl Iterator, mut compare: impl FnMut(&Item, &Item) -> Ordering, -) -> Option<(&'a Item, &'a Item)> { - match iterator.next() { - Some(item) => { - let mut min = item; - let mut max = min; - for item in iterator { - if compare(item, min) == Ordering::Less { - min = item; - } - if compare(item, max) != Ordering::Less { - max = item; - } - } - Some((min, max)) - } - None => None, - } -} - #[inline] -pub(crate) fn minmax_by_key<'a, Item: 'a, K: Ord>( - iterator: impl Iterator, mut to_key: impl FnMut(&Item) -> K, -) -> Option<(&'a Item, &'a Item)> { - minmax_by(iterator, |x, y| to_key(x).cmp(&to_key(y))) +pub(crate) fn reduce<'a, Item: 'a>( + mut iterator: impl Iterator, mut function: impl FnMut(&Item, &Item) -> Item, +) -> Option { + iterator.next().and_then(|value1| { + iterator.next().map(|value2| iterator.fold(function(value1, value2), |r, x| function(&r, x))) + }) } diff --git a/src/lib.rs b/src/lib.rs index 9a395eb..93604ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ /// # let source = vec![1, 2, 3]; /// let data = vec![1, 2, 3]; /// -/// data.fold(0, |r, x| r + x); // 6 +/// data.fold_to(0, |r, x| r + x); // 6 /// /// # let data = source.clone(); /// data.filter(|&x| x > 1); // vec![2, 3] @@ -85,7 +85,8 @@ /// | *flat_map* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | /// | *flat_map_to* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | /// | *flat* | :heavy_check_mark: | | :heavy_check_mark: | | -/// | *fold* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | +/// | *fold* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +/// | *fold_to* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | /// | *frequencies* | :heavy_check_mark: | | | | /// | *frequencies_by* | :heavy_check_mark: | | | | /// | *group_by* | :heavy_check_mark: | | :heavy_check_mark: | | @@ -133,7 +134,8 @@ /// | *product* | :heavy_check_mark: | | :heavy_check_mark: | | /// | *product_keys* | | | | :heavy_check_mark: | /// | *product_values* | | | | :heavy_check_mark: | -/// | *reduce* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | +/// | *reduce* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +/// | *reduce_to* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | /// | *replace* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | /// | *replace_all* | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | /// | *replace_at* | :heavy_check_mark: | | | | diff --git a/tests/extensions/collectible.rs b/tests/extensions/collectible.rs index 9cdd5b7..341251c 100644 --- a/tests/extensions/collectible.rs +++ b/tests/extensions/collectible.rs @@ -48,5 +48,5 @@ where // fold // assert_eq!(repeated.clone().fold(0, |r, x| r + x), 4); - assert_eq!(empty.clone().fold(0, |r, x| r + x), 0); + assert_eq!(empty.clone().fold_to(0, |r, x| r + x), 0); }