mmtk/util/rust_util/
rev_group.rs

1//! This module provides an iterator that groups adjacent items with the same key.
2//!
3//! It gives all `Iterator + Clone` iterators a new method: `.revisitable_group_by`. It is similar
4//! to `Itertools::group_by`, but it lets the user know the length of each group before iterating
5//! through the group.  Implementation-wise, it eagerly finds all items with the same key, and
6//! then lets the user traverse the same range of items again using a pre-cloned iterator. This
7//! is why it is named "revisitable" group-by.
8//!
9//! This is useful for the memory mapper to coalesce the `mmap` call for adjacent chunks that have
10//! the same `MapState`.  The memory mapper needs to know the size of each group to compute the
11//! memory range the group of `MapState` covers in order to call `mmap`, and then traverse the
12//! group of `MapState` again to update them.
13//!
14//! The `.revisitable_group_by` method takes a closure for computing the keys of each item.
15//! Adjacent items with the same key will be put into the same group.
16//!
17//! The following example groups adjacent even or odd numbers together.
18//!
19//! ```rs
20//! let nums = [1, 3, 5, 2, 4, 6, 7, 9];
21//! for group in nums.iter().revisitable_group_by(|x| *x % 2) {
22//!     println!("key: {}, len: {}", group.key, group.len);
23//!     for x in group {
24//!         println!("  x: {}", *x);
25//!     }
26//! }
27//! ```
28//!
29//! It should form three groups, `[1, 3, 5]`, `[2, 4, 6]` and `[7, 9]`, with the keys being 1, 0
30//! and 1, respectively.
31//!
32//! It can be used with the `.flatten()` method to make groups across the boundaries of several
33//! iterable items.
34//!
35//! ```rs
36//! let slice_of_slices: &[&[i32]] = &[&[10, 20], &[30, 40, 11, 21], &[31, 12, 22]];
37//! let result = slice_of_slices.iter().copied().flatten().copied()
38//!     .revisitable_group_by(|x| x % 10)
39//!     .map(|group| group.collect::<Vec<_>>())
40//!     .collect::<Vec<_>>();
41//! assert_eq!(
42//!     result,
43//!     vec![vec![10, 20, 30, 40], vec![11, 21, 31], vec![12, 22]],
44//! );
45//! ```
46
47/// This trait provides the `revisitable_group_by` method for all `Iterator` that also implements
48/// `Clone`.
49pub(crate) trait RevisitableGroupByForIterator {
50    type Item;
51    type Iter: Iterator<Item = Self::Item> + Clone;
52
53    /// Group adjacent items by key.  `get_key` is a closure that computes the key.
54    fn revisitable_group_by<K, F>(
55        self,
56        get_key: F,
57    ) -> RevisitableGroupBy<Self::Item, K, Self::Iter, F>
58    where
59        K: PartialEq + Copy,
60        F: FnMut(&Self::Item) -> K;
61}
62
63impl<I: Iterator + Clone> RevisitableGroupByForIterator for I {
64    type Item = <I as Iterator>::Item;
65    type Iter = I;
66
67    fn revisitable_group_by<K, F>(
68        self,
69        get_key: F,
70    ) -> RevisitableGroupBy<Self::Item, K, Self::Iter, F>
71    where
72        K: PartialEq + Copy,
73        F: FnMut(&Self::Item) -> K,
74    {
75        RevisitableGroupBy {
76            iter: self,
77            get_key,
78            next_group_initial: None,
79        }
80    }
81}
82
83/// An iterator through groups of items with the same key.
84pub(crate) struct RevisitableGroupBy<T, K, I, F>
85where
86    K: PartialEq + Copy,
87    I: Iterator<Item = T> + Clone,
88    F: FnMut(&T) -> K,
89{
90    /// The underlying iterator.
91    iter: I,
92    /// The function to get the key.
93    get_key: F,
94    /// Temporarily save the item and key of the next group when peeking.
95    next_group_initial: Option<(T, K)>,
96}
97
98impl<T, K, I, F> Iterator for RevisitableGroupBy<T, K, I, F>
99where
100    K: PartialEq + Copy,
101    I: Iterator<Item = T> + Clone,
102    F: FnMut(&T) -> K,
103{
104    type Item = RevisitableGroup<T, K, I>;
105
106    fn next(&mut self) -> Option<Self::Item> {
107        let (group_head, group_key) = if let Some((head, key)) = self.next_group_initial.take() {
108            // We already peeked the item of the next group the last time `next()` was called.
109            // Count that in.
110            (head, key)
111        } else {
112            // Either we haven't start iterating, yet, or we already exhausted the iter.
113            // Get the next item from the underlying iter.
114            let item = self.iter.next()?;
115            // The next group has at least one item.
116            // This is the key of the group.
117            let key = (self.get_key)(&item);
118            (item, key)
119        };
120
121        // If reached here, the group must have at least one item.
122        let mut group_size = 1;
123
124        // Get the rest of the group.
125        let saved_iter = self.iter.clone();
126        loop {
127            if let Some(item) = self.iter.next() {
128                // The next item exists. It either belongs to the current group or not.
129                let key = (self.get_key)(&item);
130                if key == group_key {
131                    // It is in the same group.
132                    group_size += 1;
133                } else {
134                    // It belongs to the next group.  Save the item and the key...
135                    self.next_group_initial = Some((item, key));
136                    // ... and we have a group now.
137                    break;
138                }
139            } else {
140                // No more items. This is the last group.
141                debug_assert!(self.next_group_initial.is_none());
142                break;
143            }
144        }
145
146        Some(RevisitableGroup {
147            key: group_key,
148            len: group_size,
149            head: Some(group_head),
150            iter: saved_iter,
151            remaining: group_size,
152        })
153    }
154}
155
156pub(crate) struct RevisitableGroup<T, K, I>
157where
158    K: PartialEq + Copy,
159    I: Iterator<Item = T>,
160{
161    /// The key of this group.
162    pub key: K,
163    /// The length of this group.
164    pub len: usize,
165    /// The first item. Note that `iter` starts from the second element due to the way we clone it.
166    head: Option<T>,
167    /// The underlying iterator.
168    iter: I,
169    /// The number of items remain to be iterated.
170    remaining: usize,
171}
172
173impl<T, K, I> Iterator for RevisitableGroup<T, K, I>
174where
175    K: PartialEq + Copy,
176    I: Iterator<Item = T>,
177{
178    type Item = T;
179
180    fn next(&mut self) -> Option<Self::Item> {
181        if self.remaining == 0 {
182            None
183        } else {
184            self.remaining -= 1;
185            if let Some(item) = self.head.take() {
186                Some(item)
187            } else {
188                let result = self.iter.next();
189                debug_assert!(result.is_some());
190                result
191            }
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_simple_group_by() {
202        let nums = [1, 3, 5, 2, 4, 6, 7, 9];
203        let grouped = nums
204            .iter()
205            .revisitable_group_by(|x| *x % 2)
206            .map(|group| (group.key, group.len, group.copied().collect::<Vec<_>>()))
207            .collect::<Vec<_>>();
208        assert_eq!(
209            grouped,
210            vec![
211                (1, 3, vec![1, 3, 5]),
212                (0, 3, vec![2, 4, 6]),
213                (1, 2, vec![7, 9]),
214            ]
215        );
216    }
217
218    #[test]
219    #[allow(clippy::never_loop)] // We are testing with empty slices. The panic in the loop body should not run.
220    fn test_empty_outer_slice() {
221        let slice_of_slices: &[&[i32]] = &[];
222        for _group in slice_of_slices
223            .iter()
224            .copied()
225            .flatten()
226            .copied()
227            .revisitable_group_by(|_| 42)
228        {
229            panic!("There is no item!");
230        }
231    }
232
233    #[test]
234    #[allow(clippy::never_loop)] // We are testing with empty slices. The panic in the loop body should not run.
235    fn test_empty_inner_slice() {
236        let slice_of_slices: &[&[i32]] = &[&[], &[], &[]];
237        for _group in slice_of_slices
238            .iter()
239            .copied()
240            .flatten()
241            .copied()
242            .revisitable_group_by(|_| 42)
243        {
244            panic!("There is no item!");
245        }
246    }
247
248    #[test]
249    fn test_single_item() {
250        let slice_of_slices: &[&[i32]] = &[&[1]];
251        for group in slice_of_slices
252            .iter()
253            .copied()
254            .flatten()
255            .copied()
256            .revisitable_group_by(|_| 42)
257        {
258            assert_eq!(group.key, 42);
259        }
260    }
261
262    #[test]
263    fn test_single_slice_multi_item() {
264        let slice_of_slices: &[&[i32]] = &[&[1, 3, 5, 2, 4, 6, 7]];
265        let result = slice_of_slices
266            .iter()
267            .copied()
268            .flatten()
269            .copied()
270            .revisitable_group_by(|x| x % 2)
271            .map(|group| (group.key, group.len, group.collect::<Vec<_>>()))
272            .collect::<Vec<_>>();
273        assert_eq!(
274            result,
275            vec![
276                (1, 3, vec![1, 3, 5]),
277                (0, 3, vec![2, 4, 6]),
278                (1, 1, vec![7])
279            ]
280        );
281    }
282
283    #[test]
284    fn test_multi_slice_multi_item() {
285        let slice_of_slices: &[&[i32]] = &[&[10, 20], &[11, 21, 31], &[12, 22, 32, 42]];
286        let result = slice_of_slices
287            .iter()
288            .copied()
289            .flatten()
290            .copied()
291            .revisitable_group_by(|x| x % 10)
292            .map(|group| (group.key, group.len, group.collect::<Vec<_>>()))
293            .collect::<Vec<_>>();
294        assert_eq!(
295            result,
296            vec![
297                (0, 2, vec![10, 20]),
298                (1, 3, vec![11, 21, 31]),
299                (2, 4, vec![12, 22, 32, 42])
300            ]
301        );
302    }
303
304    #[test]
305    fn test_cross_slice_groups() {
306        let slice_of_slices: &[&[i32]] = &[&[10, 20], &[30, 40, 11, 21], &[31, 12, 22]];
307        let result = slice_of_slices
308            .iter()
309            .copied()
310            .flatten()
311            .copied()
312            .revisitable_group_by(|x| x % 10)
313            .map(|group| (group.key, group.len, group.collect::<Vec<_>>()))
314            .collect::<Vec<_>>();
315        assert_eq!(
316            result,
317            vec![
318                (0, 4, vec![10, 20, 30, 40]),
319                (1, 3, vec![11, 21, 31]),
320                (2, 2, vec![12, 22])
321            ]
322        );
323    }
324
325    #[test]
326    fn test_cross_slice_groups2() {
327        let slice_of_slices: &[&[i32]] = &[&[10, 20, 11], &[21, 31, 41], &[51, 61], &[71, 12, 22]];
328        let result = slice_of_slices
329            .iter()
330            .cloned()
331            .flatten()
332            .copied()
333            .revisitable_group_by(|x| x % 10)
334            .map(|group| (group.key, group.len, group.collect::<Vec<_>>()))
335            .collect::<Vec<_>>();
336        assert_eq!(
337            result,
338            vec![
339                (0, 2, vec![10, 20]),
340                (1, 7, vec![11, 21, 31, 41, 51, 61, 71]),
341                (2, 2, vec![12, 22])
342            ]
343        );
344    }
345
346    #[test]
347    fn test_internal_mutability() {
348        use std::sync::atomic::{AtomicUsize, Ordering};
349        let slab0 = vec![
350            AtomicUsize::new(1),
351            AtomicUsize::new(3),
352            AtomicUsize::new(2),
353        ];
354        let slab1 = vec![
355            AtomicUsize::new(4),
356            AtomicUsize::new(6),
357            AtomicUsize::new(5),
358        ];
359        let slab2 = vec![
360            AtomicUsize::new(7),
361            AtomicUsize::new(9),
362            AtomicUsize::new(10),
363        ];
364
365        // Note: We only take the first two elements from slab2,
366        // because the mmapper sometimes processes part of a slab.
367        let slices: Vec<&[AtomicUsize]> = vec![&slab0[0..3], &slab1[0..3], &slab2[0..2]];
368
369        let mut collected = vec![];
370
371        for group in slices
372            .iter()
373            .copied()
374            .flatten()
375            .revisitable_group_by(|x| x.load(Ordering::SeqCst) % 2)
376        {
377            let mut group_collected = vec![];
378            let key = group.key;
379            for elem in group {
380                let value = elem.load(Ordering::SeqCst);
381                group_collected.push(value);
382
383                let new_value = value * 100 + key;
384                elem.store(new_value, Ordering::SeqCst);
385            }
386
387            collected.push(group_collected);
388        }
389
390        assert_eq!(collected, vec![vec![1, 3], vec![2, 4, 6], vec![5, 7, 9]]);
391
392        let load_all = |slab: Vec<AtomicUsize>| {
393            slab.iter()
394                .map(|x| x.load(Ordering::SeqCst))
395                .collect::<Vec<_>>()
396        };
397
398        assert_eq!(load_all(slab0), vec![101, 301, 200]);
399        assert_eq!(load_all(slab1), vec![400, 600, 501]);
400        assert_eq!(load_all(slab2), vec![701, 901, 10]); // The last item should not be affected.
401    }
402}