Skip to content

Commit 5c8c197

Browse files
authored
Remove Clone requirement from balanced reductions (#9442)
This seems an artifact of the implementation and not actual requirement --------- Signed-off-by: Robert Kruszewski <github@robertk.io>
1 parent db781b0 commit 5c8c197

1 file changed

Lines changed: 37 additions & 46 deletions

File tree

vortex-utils/src/iter.rs

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
//! Iterator extension traits.
55
6+
use std::convert::Infallible;
7+
68
/// An extension trait for iterators that provides balanced binary tree reduction.
79
///
810
/// Unlike [`Iterator::reduce`], which builds a left-leaning linear chain of depth N,
@@ -27,58 +29,29 @@ pub trait ReduceBalancedIterExt: Iterator {
2729
/// Returns `None` if the iterator is empty.
2830
fn reduce_balanced<F>(self, combine: F) -> Option<Self::Item>
2931
where
30-
Self::Item: Clone,
3132
F: Fn(Self::Item, Self::Item) -> Self::Item;
3233

3334
/// Fallible version of [`reduce_balanced`](ReduceBalancedIterExt::reduce_balanced).
3435
///
3536
/// Short-circuits on the first error.
3637
fn try_reduce_balanced<F, E>(self, combine: F) -> Result<Option<Self::Item>, E>
3738
where
38-
Self::Item: Clone,
3939
F: Fn(Self::Item, Self::Item) -> Result<Self::Item, E>;
4040
}
4141

4242
impl<I: Iterator + Sized> ReduceBalancedIterExt for I {
4343
fn reduce_balanced<F>(self, combine: F) -> Option<Self::Item>
4444
where
45-
Self::Item: Clone,
4645
F: Fn(Self::Item, Self::Item) -> Self::Item,
4746
{
48-
let mut items: Vec<_> = self.collect();
49-
if items.is_empty() {
50-
return None;
51-
}
52-
if items.len() == 1 {
53-
return items.pop();
54-
}
55-
56-
while items.len() > 1 {
57-
let len = items.len();
58-
59-
for target_idx in 0..(len / 2) {
60-
let item_idx = target_idx * 2;
61-
let new = combine(items[item_idx].clone(), items[item_idx + 1].clone());
62-
items[target_idx] = new;
63-
}
64-
65-
if !len.is_multiple_of(2) {
66-
// Merge the odd element into the last paired element so it stays inside the tree.
67-
let lhs = items[(len / 2) - 1].clone();
68-
let rhs = items[len - 1].clone();
69-
items[len / 2 - 1] = combine(lhs, rhs);
70-
}
71-
72-
items.truncate(len / 2);
47+
match self.try_reduce_balanced(|lhs, rhs| Ok::<_, Infallible>(combine(lhs, rhs))) {
48+
Ok(result) => result,
49+
Err(never) => match never {},
7350
}
74-
75-
assert_eq!(items.len(), 1);
76-
items.pop()
7751
}
7852

7953
fn try_reduce_balanced<F, E>(self, combine: F) -> Result<Option<Self::Item>, E>
8054
where
81-
Self::Item: Clone,
8255
F: Fn(Self::Item, Self::Item) -> Result<Self::Item, E>,
8356
{
8457
let mut items: Vec<_> = self.collect();
@@ -89,22 +62,27 @@ impl<I: Iterator + Sized> ReduceBalancedIterExt for I {
8962
return Ok(items.pop());
9063
}
9164

65+
// Each pass consumes one level of the reduction tree, combining adjacent pairs into the
66+
// next level. The two vectors swap roles between passes so items can be moved rather than
67+
// cloned while retaining their allocations.
68+
let mut next = Vec::with_capacity(items.len() / 2);
9269
while items.len() > 1 {
93-
let len = items.len();
94-
95-
for target_idx in 0..(len / 2) {
96-
let item_idx = target_idx * 2;
97-
let new = combine(items[item_idx].clone(), items[item_idx + 1].clone())?;
98-
items[target_idx] = new;
70+
next.clear();
71+
let mut iter = items.drain(..);
72+
while let Some(lhs) = iter.next() {
73+
if let Some(rhs) = iter.next() {
74+
next.push(combine(lhs, rhs)?);
75+
} else {
76+
// Folding an odd tail into the preceding pair keeps it at the current tree
77+
// level instead of carrying it forward as a shallower subtree.
78+
let Some(previous) = next.pop() else {
79+
unreachable!("a reduction level with an odd tail has a preceding pair")
80+
};
81+
next.push(combine(previous, lhs)?);
82+
}
9983
}
100-
101-
if !len.is_multiple_of(2) {
102-
let lhs = items[(len / 2) - 1].clone();
103-
let rhs = items[len - 1].clone();
104-
items[len / 2 - 1] = combine(lhs, rhs)?;
105-
}
106-
107-
items.truncate(len / 2);
84+
drop(iter);
85+
std::mem::swap(&mut items, &mut next);
10886
}
10987

11088
assert_eq!(items.len(), 1);
@@ -177,6 +155,19 @@ mod tests {
177155
assert_eq!(result, Some("((a+b)+((c+d)+e))".to_string()));
178156
}
179157

158+
#[test]
159+
fn test_non_clone_items() {
160+
#[derive(Debug, PartialEq, Eq)]
161+
struct NonClone(String);
162+
163+
let result = ["a", "b", "c"]
164+
.into_iter()
165+
.map(|value| NonClone(value.to_owned()))
166+
.reduce_balanced(|NonClone(lhs), NonClone(rhs)| NonClone(format!("({lhs}+{rhs})")));
167+
168+
assert_eq!(result, Some(NonClone("((a+b)+c)".to_owned())));
169+
}
170+
180171
#[test]
181172
fn test_try_reduce_balanced_ok() {
182173
let result: Result<_, &str> = [1, 2, 3, 4]

0 commit comments

Comments
 (0)