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
4242impl < 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