Skip to content

Commit 4e13d04

Browse files
rbruenigPhilippe-Cholet
authored andcommitted
improve documentation of tree_reduce
1 parent b86960a commit 4e13d04

File tree

1 file changed

+41
-12
lines changed

1 file changed

+41
-12
lines changed

src/lib.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2528,34 +2528,63 @@ pub trait Itertools: Iterator {
25282528
///
25292529
/// If `f` is associative you should also decide carefully:
25302530
///
2531-
/// - if `f` is a trivial operation like `u32::wrapping_add`, prefer the normal
2532-
/// [`Iterator::reduce`] instead since it will most likely result in the generation of simpler
2533-
/// code because the compiler is able to optimize it
2534-
/// - otherwise if `f` is non-trivial like `format!`, you should use `tree_reduce` since it
2535-
/// reduces the number of operations from `O(n)` to `O(ln(n))`
2531+
/// For an iterator producing `n` elements, both [`Iterator::reduce`] and `tree_reduce` will
2532+
/// call `f` `n - 1` times. However, `tree_reduce` will call `f` on earlier intermediate
2533+
/// results, which is beneficial for `f` that allocate and produce longer results for longer
2534+
/// arguments. For example if `f` combines arguments using `format!`, then `tree_reduce` will
2535+
/// operate on average on shorter arguments resulting in less bytes being allocated overall.
25362536
///
2537-
/// Here "non-trivial" means:
2538-
///
2539-
/// - any allocating operation
2540-
/// - any function that is a composition of many operations
2537+
/// If 'f' does not benefit from such a reordering, like `u32::wrapping_add`, prefer the
2538+
/// normal [`Iterator::reduce`] instead since it will most likely result in the generation of
2539+
/// simpler code because the compiler is able to optimize it.
25412540
///
25422541
/// ```
25432542
/// use itertools::Itertools;
25442543
///
2544+
/// let f = |a: String, b: String| {
2545+
/// format!("f({a}, {b})")
2546+
/// };
2547+
///
25452548
/// // The same tree as above
2546-
/// let num_strings = (1..8).map(|x| x.to_string());
2547-
/// assert_eq!(num_strings.tree_reduce(|x, y| format!("f({}, {})", x, y)),
2548-
/// Some(String::from("f(f(f(1, 2), f(3, 4)), f(f(5, 6), 7))")));
2549+
/// assert_eq!((1..8).map(|x| x.to_string()).tree_reduce(f),
2550+
/// Some(String::from("f(f(f(1, 2), f(3, 4)), f(f(5, 6), 7))")));
25492551
///
25502552
/// // Like fold1, an empty iterator produces None
25512553
/// assert_eq!((0..0).tree_reduce(|x, y| x * y), None);
25522554
///
25532555
/// // tree_reduce matches fold1 for associative operations...
25542556
/// assert_eq!((0..10).tree_reduce(|x, y| x + y),
25552557
/// (0..10).fold1(|x, y| x + y));
2558+
///
25562559
/// // ...but not for non-associative ones
25572560
/// assert_ne!((0..10).tree_reduce(|x, y| x - y),
25582561
/// (0..10).fold1(|x, y| x - y));
2562+
///
2563+
///
2564+
/// let mut total_capacity_reduce = 0;
2565+
/// let reduce_res = (1..100).map(|x| x.to_string())
2566+
/// .reduce(|a, b| {
2567+
/// let r = f(a, b);
2568+
/// total_capacity_reduce += r.capacity();
2569+
/// r
2570+
/// })
2571+
/// .unwrap();
2572+
///
2573+
/// let mut total_capacity_tree_reduce = 0;
2574+
/// let tree_reduce_res = (1..100).map(|x| x.to_string())
2575+
/// .tree_reduce(|a, b| {
2576+
/// let r = f(a, b);
2577+
/// total_capacity_tree_reduce += r.capacity();
2578+
/// r
2579+
/// })
2580+
/// .unwrap();
2581+
///
2582+
/// dbg!(total_capacity_reduce, total_capacity_tree_reduce,
2583+
/// reduce_res.len(), tree_reduce_res.len());
2584+
/// // total_capacity_reduce = 65630
2585+
/// // total_capacity_tree_reduce = 7284
2586+
/// // reduce_res.len() = 679
2587+
/// // tree_reduce_res.len() = 679
25592588
/// ```
25602589
fn tree_reduce<F>(mut self, mut f: F) -> Option<Self::Item>
25612590
where

0 commit comments

Comments
 (0)