1use std::convert::Infallible;
7
8pub trait ReduceBalancedIterExt: Iterator {
25 fn reduce_balanced<F>(self, combine: F) -> Option<Self::Item>
31 where
32 F: Fn(Self::Item, Self::Item) -> Self::Item;
33
34 fn try_reduce_balanced<F, E>(self, combine: F) -> Result<Option<Self::Item>, E>
38 where
39 F: Fn(Self::Item, Self::Item) -> Result<Self::Item, E>;
40}
41
42impl<I: Iterator + Sized> ReduceBalancedIterExt for I {
43 fn reduce_balanced<F>(self, combine: F) -> Option<Self::Item>
44 where
45 F: Fn(Self::Item, Self::Item) -> Self::Item,
46 {
47 match self.try_reduce_balanced(|lhs, rhs| Ok::<_, Infallible>(combine(lhs, rhs))) {
48 Ok(result) => result,
49 Err(never) => match never {},
50 }
51 }
52
53 fn try_reduce_balanced<F, E>(self, combine: F) -> Result<Option<Self::Item>, E>
54 where
55 F: Fn(Self::Item, Self::Item) -> Result<Self::Item, E>,
56 {
57 let mut items: Vec<_> = self.collect();
58 if items.is_empty() {
59 return Ok(None);
60 }
61 if items.len() == 1 {
62 return Ok(items.pop());
63 }
64
65 let mut next = Vec::with_capacity(items.len() / 2);
69 while items.len() > 1 {
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 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 }
83 }
84 drop(iter);
85 std::mem::swap(&mut items, &mut next);
86 }
87
88 assert_eq!(items.len(), 1);
89 Ok(items.pop())
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_empty() {
99 let result = std::iter::empty::<i32>().reduce_balanced(|a, b| a + b);
100 assert_eq!(result, None);
101 }
102
103 #[test]
104 fn test_single() {
105 let result = [42].into_iter().reduce_balanced(|a, b| a + b);
106 assert_eq!(result, Some(42));
107 }
108
109 #[test]
110 fn test_two() {
111 let result = [1, 2].into_iter().reduce_balanced(|a, b| a + b);
112 assert_eq!(result, Some(3));
113 }
114
115 #[test]
116 fn test_power_of_two() {
117 let result = [1, 2, 3, 4].into_iter().reduce_balanced(|a, b| a + b);
118 assert_eq!(result, Some(10));
119 }
120
121 #[test]
122 fn test_odd_count() {
123 let result = [1, 2, 3, 4, 5].into_iter().reduce_balanced(|a, b| a + b);
124 assert_eq!(result, Some(15));
125 }
126
127 #[test]
128 fn test_balanced_structure() {
129 let result = ["a", "b", "c", "d"]
132 .into_iter()
133 .map(String::from)
134 .reduce_balanced(|a, b| format!("({a}+{b})"));
135 assert_eq!(result, Some("((a+b)+(c+d))".to_string()));
136 }
137
138 #[test]
139 fn test_balanced_structure_odd() {
140 let result = ["a", "b", "c"]
142 .into_iter()
143 .map(String::from)
144 .reduce_balanced(|a, b| format!("({a}+{b})"));
145 assert_eq!(result, Some("((a+b)+c)".to_string()));
146 }
147
148 #[test]
149 fn test_balanced_structure_five() {
150 let result = ["a", "b", "c", "d", "e"]
152 .into_iter()
153 .map(String::from)
154 .reduce_balanced(|a, b| format!("({a}+{b})"));
155 assert_eq!(result, Some("((a+b)+((c+d)+e))".to_string()));
156 }
157
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
171 #[test]
172 fn test_try_reduce_balanced_ok() {
173 let result: Result<_, &str> = [1, 2, 3, 4]
174 .into_iter()
175 .try_reduce_balanced(|a, b| Ok(a + b));
176 assert_eq!(result, Ok(Some(10)));
177 }
178
179 #[test]
180 fn test_try_reduce_balanced_err() {
181 let result: Result<Option<i32>, &str> = [1, 2, 3, 4]
182 .into_iter()
183 .try_reduce_balanced(|a, b| if a + b > 4 { Err("too big") } else { Ok(a + b) });
184 assert_eq!(result, Err("too big"));
185 }
186
187 #[test]
188 fn test_try_reduce_balanced_empty() {
189 let result: Result<_, &str> =
190 std::iter::empty::<i32>().try_reduce_balanced(|a, b| Ok(a + b));
191 assert_eq!(result, Ok(None));
192 }
193
194 #[test]
195 fn test_try_reduce_balanced_single() {
196 let result: Result<_, &str> = [42].into_iter().try_reduce_balanced(|a, b| Ok(a + b));
197 assert_eq!(result, Ok(Some(42)));
198 }
199}