equal_parts/lib.rs
1/// A trait for splitting collections into approximately equal parts.
2///
3/// This trait provides functionality to divide a collection into a specified number
4/// of parts, where each part contains roughly the same number of elements. When the
5/// total number of elements doesn't divide evenly, some parts will be one element
6/// larger than others.
7///
8/// # Examples
9///
10/// Basic usage with slices:
11///
12/// ```
13/// use equal_parts::EqualParts;
14///
15/// let data = vec![1, 2, 3, 4, 5, 6];
16/// let parts: Vec<&[i32]> = data.equal_parts(3).collect();
17/// assert_eq!(parts, vec![&[1, 2], &[3, 4], &[5, 6]]);
18/// ```
19///
20/// Handling uneven divisions:
21///
22/// ```
23/// use equal_parts::EqualParts;
24///
25/// let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
26/// let mut iter = data.equal_parts(4);
27/// // First parts get extra elements when the division isn't even.
28/// assert_eq!(iter.next(), Some([1, 2, 3].as_slice()));
29/// assert_eq!(iter.next(), Some([4, 5, 6].as_slice()));
30/// // The size of the parts only changes once, and only by one element.
31/// assert_eq!(iter.next(), Some([7, 8].as_slice()));
32/// assert_eq!(iter.next(), Some([9, 10].as_slice()));
33/// assert_eq!(iter.next(), None);
34/// ```
35///
36/// When there are fewer elements than requested parts:
37///
38/// ```
39/// use equal_parts::EqualParts;
40///
41/// let data = [1, 2];
42/// let parts: Vec<&[i32]> = data.as_slice().equal_parts(5).collect();
43/// // Only returns as many parts as there are elements
44/// assert_eq!(parts, vec![&[1], &[2]]);
45/// ```
46pub trait EqualParts {
47 /// The type of items yielded by the iterator.
48 type Item;
49
50 /// The iterator type returned by [`equal_parts`](Self::equal_parts).
51 type Iter: Iterator<Item = Self::Item>;
52
53 /// Splits the collection into approximately equal parts.
54 ///
55 /// Returns an iterator that yields each part as a separate item. The parts
56 /// will be as equal in size as possible, with larger parts appearing first
57 /// when the total length doesn't divide evenly.
58 ///
59 /// # Arguments
60 ///
61 /// * `num_parts` - The number of parts to split the collection into.
62 /// Must be greater than 0.
63 ///
64 /// # Panics
65 ///
66 /// Panics if `num_parts` is 0.
67 ///
68 /// # Examples
69 ///
70 /// ```
71 /// use equal_parts::EqualParts;
72 ///
73 /// let data = [1, 2, 3, 4, 5, 6, 7, 8];
74 /// let mut parts = data.as_slice().equal_parts(3);
75 ///
76 /// assert_eq!(parts.next(), Some([1, 2, 3].as_slice()));
77 /// assert_eq!(parts.next(), Some([4, 5, 6].as_slice()));
78 /// assert_eq!(parts.next(), Some([7, 8].as_slice()));
79 /// assert_eq!(parts.next(), None);
80 /// ```
81 fn equal_parts(self, num_parts: usize) -> Self::Iter;
82}
83
84/// Iterator that yields approximately equal parts of a slice.
85///
86/// This iterator is created by calling [`equal_parts`](EqualParts::equal_parts) on a slice.
87/// It yields each part as a `&[T]` slice reference.
88///
89/// The iterator ensures that:
90/// - All parts have roughly the same size
91/// - When the total length doesn't divide evenly, larger parts come first
92/// - The iterator stops when all elements have been consumed
93///
94/// # Examples
95///
96/// ```
97/// use equal_parts::EqualParts;
98///
99/// let data = [1, 2, 3, 4, 5, 6, 7];
100/// let mut iter = data.as_slice().equal_parts(3);
101///
102/// assert_eq!(iter.next(), Some([1, 2, 3].as_slice()));
103/// assert_eq!(iter.next(), Some([4, 5].as_slice()));
104/// assert_eq!(iter.next(), Some([6, 7].as_slice()));
105/// assert_eq!(iter.next(), None);
106/// ```
107pub struct EqualPartsIter<'a, T> {
108 data: &'a [T],
109 part_size: usize,
110 full_parts_left: usize,
111}
112
113impl<'a, T> Iterator for EqualPartsIter<'a, T> {
114 type Item = &'a [T];
115
116 fn next(&mut self) -> Option<Self::Item> {
117 if self.data.is_empty() {
118 None
119 } else {
120 let split_point = self.part_size - (self.full_parts_left.min(1) ^ 1);
121 self.full_parts_left -= self.full_parts_left.min(1);
122
123 let (chunk, rest) = self.data.split_at(split_point);
124 self.data = rest;
125 Some(chunk)
126 }
127 }
128}
129
130impl<'a, T> EqualParts for &'a [T] {
131 type Item = &'a [T];
132 type Iter = EqualPartsIter<'a, T>;
133
134 fn equal_parts(self, num_parts: usize) -> Self::Iter {
135 let part_size = self.len().div_ceil(num_parts);
136 let small_part_count = part_size * num_parts - self.len();
137 EqualPartsIter {
138 data: self,
139 part_size,
140 full_parts_left: num_parts - small_part_count,
141 }
142 }
143}
144
145impl<'a, T> EqualParts for &'a Vec<T> {
146 type Item = &'a [T];
147 type Iter = EqualPartsIter<'a, T>;
148
149 fn equal_parts(self, num_parts: usize) -> Self::Iter {
150 self.as_slice().equal_parts(num_parts)
151 }
152}
153
154// Also include the IntoEqualParts trait
155pub mod into;
156pub use crate::into::into_equal_parts::IntoEqualParts;
157
158#[cfg(test)]
159mod tests {
160 use super::EqualParts;
161
162 #[test]
163 fn simple_equal_parts() {
164 let data: &[i32] = &[1, 2, 3, 4, 5, 6];
165 let mut parts = data.equal_parts(3);
166 assert_eq!(parts.next(), Some([1, 2].as_slice()));
167 assert_eq!(parts.next(), Some([3, 4].as_slice()));
168 assert_eq!(parts.next(), Some([5, 6].as_slice()));
169 assert_eq!(parts.next(), None);
170 }
171
172 #[test]
173 fn uneven_equal_parts() {
174 let data: &[i32] = &[1, 2, 3, 4, 5, 6, 7];
175 let mut parts = data.equal_parts(3);
176 assert_eq!(parts.next(), Some([1, 2, 3].as_slice()));
177 assert_eq!(parts.next(), Some([4, 5].as_slice()));
178 assert_eq!(parts.next(), Some([6, 7].as_slice()));
179 assert_eq!(parts.next(), None);
180 }
181
182 #[test]
183 fn not_enough_parts() {
184 let data: &[i32] = &[1, 2];
185 let mut parts = data.equal_parts(3);
186 assert_eq!(parts.next(), Some([1].as_slice()));
187 assert_eq!(parts.next(), Some([2].as_slice()));
188 assert_eq!(parts.next(), None);
189 }
190
191 #[test]
192 fn empty_data() {
193 let data: &[i32] = &[];
194 let mut parts = data.equal_parts(3);
195 assert_eq!(parts.next(), None);
196 }
197
198 #[test]
199 #[should_panic]
200 fn panics_with_zero_parts() {
201 let data: &[i32] = &[1, 2, 3];
202 let _ = data.equal_parts(0);
203 }
204
205 #[test]
206 fn works_on_vec() {
207 let data = vec![1, 2, 3, 4, 5, 6];
208 let mut parts = data.equal_parts(3);
209 assert_eq!(parts.next(), Some([1, 2].as_slice()));
210 assert_eq!(parts.next(), Some([3, 4].as_slice()));
211 assert_eq!(parts.next(), Some([5, 6].as_slice()));
212 assert_eq!(parts.next(), None);
213 }
214}