ExamplesBy LevelBy TopicLearning Paths
099 Intermediate

099 — Group Consecutive Equal Elements

Functional Programming

Tutorial

The Problem

Group consecutive equal elements of a slice into a Vec<Vec<T>>. Adjacent identical elements form one group; a new group starts when the element changes. Input [1, 1, 2, 2, 2, 3, 1, 1] produces [[1,1], [2,2,2], [3], [1,1]]. Compare with OCaml's accumulator-based recursive approach.

🎯 Learning Outcomes

  • • Use Vec<Vec<T>> as the accumulator for grouped output
  • • Use groups.last_mut().unwrap() to extend the current group in-place
  • • Start a new group with groups.push(vec![item.clone()]) when the element changes
  • • Apply T: PartialEq + Clone for equality comparison and group construction
  • • Map Rust's mutable Vec accumulator to OCaml's pure recursive accumulator
  • • Recognise this as the building block for RLE (run-length encoding)
  • Code Example

    #![allow(clippy::all)]
    // 099: Group Consecutive Equal Elements
    
    fn group_by<T: PartialEq + Clone>(v: &[T]) -> Vec<Vec<T>> {
        if v.is_empty() {
            return vec![];
        }
        let mut groups: Vec<Vec<T>> = vec![vec![v[0].clone()]];
        for item in &v[1..] {
            if item == groups.last().unwrap().last().unwrap() {
                groups.last_mut().unwrap().push(item.clone());
            } else {
                groups.push(vec![item.clone()]);
            }
        }
        groups
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_group_by() {
            assert_eq!(
                group_by(&[1, 1, 2, 2, 2, 3, 1, 1]),
                vec![vec![1, 1], vec![2, 2, 2], vec![3], vec![1, 1]]
            );
        }
    
        #[test]
        fn test_empty() {
            assert_eq!(group_by::<i32>(&[]), Vec::<Vec<i32>>::new());
        }
    
        #[test]
        fn test_single() {
            assert_eq!(group_by(&[1]), vec![vec![1]]);
        }
    
        #[test]
        fn test_all_same() {
            assert_eq!(group_by(&[5, 5, 5]), vec![vec![5, 5, 5]]);
        }
    }

    Key Differences

    AspectRustOCaml
    StateMutable Vec<Vec<T>>Immutable aux current group acc
    Current grouplast_mut().unwrap().push(x)Prepend to group list
    New grouppush(vec![x])Recurse with new current
    ReversalNot neededList.rev for final group
    CloneRequired (T: Clone)Value semantics (copy by default)
    PatternMutable accumulationTail-recursive accumulation

    Group-by consecutive is the inverse of run-length encoding. The data structure patterns here — nested mutable Vec, or accumulator-threaded recursion — appear throughout list processing and string parsing.

    OCaml Approach

    OCaml's group_by uses a recursive aux current group acc accumulator. current is the value being accumulated, group is the current run (reversed), and acc holds completed groups. At the end, List.rev (current :: group) reconstructs the final group in order. Each step either extends the current group or finalises it and starts a new one.

    Full Source

    #![allow(clippy::all)]
    // 099: Group Consecutive Equal Elements
    
    fn group_by<T: PartialEq + Clone>(v: &[T]) -> Vec<Vec<T>> {
        if v.is_empty() {
            return vec![];
        }
        let mut groups: Vec<Vec<T>> = vec![vec![v[0].clone()]];
        for item in &v[1..] {
            if item == groups.last().unwrap().last().unwrap() {
                groups.last_mut().unwrap().push(item.clone());
            } else {
                groups.push(vec![item.clone()]);
            }
        }
        groups
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_group_by() {
            assert_eq!(
                group_by(&[1, 1, 2, 2, 2, 3, 1, 1]),
                vec![vec![1, 1], vec![2, 2, 2], vec![3], vec![1, 1]]
            );
        }
    
        #[test]
        fn test_empty() {
            assert_eq!(group_by::<i32>(&[]), Vec::<Vec<i32>>::new());
        }
    
        #[test]
        fn test_single() {
            assert_eq!(group_by(&[1]), vec![vec![1]]);
        }
    
        #[test]
        fn test_all_same() {
            assert_eq!(group_by(&[5, 5, 5]), vec![vec![5, 5, 5]]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_group_by() {
            assert_eq!(
                group_by(&[1, 1, 2, 2, 2, 3, 1, 1]),
                vec![vec![1, 1], vec![2, 2, 2], vec![3], vec![1, 1]]
            );
        }
    
        #[test]
        fn test_empty() {
            assert_eq!(group_by::<i32>(&[]), Vec::<Vec<i32>>::new());
        }
    
        #[test]
        fn test_single() {
            assert_eq!(group_by(&[1]), vec![vec![1]]);
        }
    
        #[test]
        fn test_all_same() {
            assert_eq!(group_by(&[5, 5, 5]), vec![vec![5, 5, 5]]);
        }
    }

    Deep Comparison

    Core Insight

    Grouping consecutive elements is useful for RLE encoding, data aggregation, and stream processing

    OCaml Approach

  • • See example.ml for implementation
  • Rust Approach

  • • See example.rs for implementation
  • Comparison Table

    FeatureOCamlRust
    Seeexample.mlexample.rs

    Exercises

  • Implement group_by_key<T, K: PartialEq, F: Fn(&T) -> K>(v: &[T], f: F) -> Vec<Vec<T>> that groups by a derived key.
  • Combine group_by with .map(|g| (g[0].clone(), g.len())) to implement run-length encoding.
  • Write a version that returns Vec<(T, usize)> (value, count) instead of Vec<Vec<T>>.
  • Implement group_by_windows(v: &[T], k: usize) -> Vec<&[T]> that groups by non-overlapping windows of size k.
  • In OCaml, implement group_by_seq : 'a Seq.t -> 'a list Seq.t for lazy grouped output.
  • Open Source Repos