099 — Group Consecutive Equal Elements
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
Vec<Vec<T>> as the accumulator for grouped outputgroups.last_mut().unwrap() to extend the current group in-placegroups.push(vec![item.clone()]) when the element changesT: PartialEq + Clone for equality comparison and group constructionVec accumulator to OCaml's pure recursive accumulatorCode 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
| Aspect | Rust | OCaml |
|---|---|---|
| State | Mutable Vec<Vec<T>> | Immutable aux current group acc |
| Current group | last_mut().unwrap().push(x) | Prepend to group list |
| New group | push(vec![x]) | Recurse with new current |
| Reversal | Not needed | List.rev for final group |
| Clone | Required (T: Clone) | Value semantics (copy by default) |
| Pattern | Mutable accumulation | Tail-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]]);
}
}#[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
Rust Approach
Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| See | example.ml | example.rs |
Exercises
group_by_key<T, K: PartialEq, F: Fn(&T) -> K>(v: &[T], f: F) -> Vec<Vec<T>> that groups by a derived key.group_by with .map(|g| (g[0].clone(), g.len())) to implement run-length encoding.Vec<(T, usize)> (value, count) instead of Vec<Vec<T>>.group_by_windows(v: &[T], k: usize) -> Vec<&[T]> that groups by non-overlapping windows of size k.group_by_seq : 'a Seq.t -> 'a list Seq.t for lazy grouped output.