@@ -2528,34 +2528,63 @@ pub trait Itertools: Iterator {
2528
2528
///
2529
2529
/// If `f` is associative you should also decide carefully:
2530
2530
///
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.
2536
2536
///
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.
2541
2540
///
2542
2541
/// ```
2543
2542
/// use itertools::Itertools;
2544
2543
///
2544
+ /// let f = |a: String, b: String| {
2545
+ /// format!("f({a}, {b})")
2546
+ /// };
2547
+ ///
2545
2548
/// // 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))")));
2549
2551
///
2550
2552
/// // Like fold1, an empty iterator produces None
2551
2553
/// assert_eq!((0..0).tree_reduce(|x, y| x * y), None);
2552
2554
///
2553
2555
/// // tree_reduce matches fold1 for associative operations...
2554
2556
/// assert_eq!((0..10).tree_reduce(|x, y| x + y),
2555
2557
/// (0..10).fold1(|x, y| x + y));
2558
+ ///
2556
2559
/// // ...but not for non-associative ones
2557
2560
/// assert_ne!((0..10).tree_reduce(|x, y| x - y),
2558
2561
/// (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
2559
2588
/// ```
2560
2589
fn tree_reduce < F > ( mut self , mut f : F ) -> Option < Self :: Item >
2561
2590
where
0 commit comments