Skip to main content

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}