← run

rs-04-group-consecutive

1.000
7/7 tests· algorithms
Challenge · difficulty 4/5
# Group consecutive runs

Implement the library file **`src/lib.rs`** exposing:

```rust
pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)>
```

Collapse runs of consecutive equal elements (a form of run-length encoding).
Walk `items` left to right; each maximal run of equal adjacent elements becomes a
single `(value, run_length)` pair in the output. Pairs appear in the same order
the runs occur, and `run_length` is always at least `1`.

The function is **generic**: it must work for any element type `T` that is
`PartialEq + Clone` (e.g. `i32`, `char`, `String`). Use `PartialEq` to compare
adjacent elements and `Clone` to copy the representative value into the result.
Do not require `T: Copy`, `T: Hash`, or `T: Ord`.

Behavior:

- Empty input returns an empty `Vec`.
- Only **consecutive** equal elements are merged; equal elements separated by a
  different element form separate runs.

Examples:

- `group_consecutive(&[1, 1, 2, 3, 3, 3])` → `vec![(1, 2), (2, 1), (3, 3)]`
- `group_consecutive(&['a', 'a', 'b', 'a'])` → `vec![('a', 2), ('b', 1), ('a', 1)]`
- `group_consecutive::<i32>(&[])` → `vec![]`
- `group_consecutive(&[1, 2, 3])` → `vec![(1, 1), (2, 1), (3, 1)]`
- `group_consecutive(&[7, 7, 7])` → `vec![(7, 3)]`

Use only the standard library. Tests live in `tests/` and call it as
`challenge::group_consecutive`.
tests/group.rs
use challenge::group_consecutive;

#[test]
fn ints_with_runs() {
    assert_eq!(
        group_consecutive(&[1, 1, 2, 3, 3, 3]),
        vec![(1, 2), (2, 1), (3, 3)]
    );
}

#[test]
fn chars_prove_generic() {
    assert_eq!(
        group_consecutive(&['a', 'a', 'b', 'a']),
        vec![('a', 2), ('b', 1), ('a', 1)]
    );
}

#[test]
fn works_for_owned_strings() {
    let v = vec!["x".to_string(), "x".to_string(), "y".to_string()];
    assert_eq!(
        group_consecutive(&v),
        vec![("x".to_string(), 2), ("y".to_string(), 1)]
    );
}

#[test]
fn empty_input() {
    let out = group_consecutive::<i32>(&[]);
    assert_eq!(out, Vec::<(i32, usize)>::new());
}

#[test]
fn all_distinct() {
    assert_eq!(
        group_consecutive(&[1, 2, 3]),
        vec![(1, 1), (2, 1), (3, 1)]
    );
}

#[test]
fn all_same() {
    assert_eq!(group_consecutive(&[7, 7, 7]), vec![(7, 3)]);
}

#[test]
fn single_element() {
    assert_eq!(group_consecutive(&[42]), vec![(42, 1)]);
}
System prompt
You are an expert programmer. Solve the task exactly as specified. Output your solution as fenced code blocks using the required file name(s) and the exact function/type signatures requested. Prefer correctness; do not include prose outside code unless asked.
Proposed solution
```rust
pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)> {
    if items.is_empty() {
        return Vec::new();
    }

    let mut result = Vec::new();
    let mut current_val = items[0].clone();
    let mut count = 1;

    for item in &items[1..] {
        if *item == current_val {
            count += 1;
        } else {
            result.push((current_val.clone(), count));
            current_val = item.clone();
            count = 1;
        }
    }
    result.push((current_val, count));
    result
}
```

Test output (stdout)


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


running 7 tests
.......
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Reasoning (chain-of-thought)
The user wants me to implement a Rust function `group_consecutive` in `src/lib.rs`.
The function signature is:
```rust
pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)>
```

It should perform run-length encoding on consecutive equal elements.
Constraints:
- Generic over `T: PartialEq + Clone`
- Empty input returns empty `Vec`
- Only consecutive equal elements are merged
- Use only standard library
- Tests will call it as `challenge::group_consecutive`

Let's implement it.
We can iterate over the slice, keep track of the current run's value and count, and push to a result vector when the run ends or at the end of the slice.

Algorithm:
1. If `items` is empty, return `vec![]`.
2. Initialize `result` as an empty `Vec`.
3. Clone the first element as `current_val`.
4. Initialize `count` to 1.
5. Iterate over the rest of the elements (`items[1..]` or using an iterator).
6. For each element, if it equals `current_val`, increment `count`.
7. Else, push `(current_val.clone(), count)` to `result`, update `current_val` to the new element, reset `count` to 1.
8. After the loop, push the last run `(current_val, count)` to `result`.
9. Return `result`.

Let's write it out carefully.

```rust
pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)> {
    if items.is_empty() {
        return Vec::new();
    }

    let mut result = Vec::new();
    let mut current_val = items[0].clone();
    let mut count = 1;

    for item in &items[1..] {
        if *item == current_val {
            count += 1;
        } else {
            result.push((current_val.clone(), count));
            current_val = item.clone();
            count = 1;
        }
    }
    result.push((current_val, count));
    result
}
```

Wait, `items[1..]` creates a slice, which is fine. Alternatively, we can use an iterator to avoid slicing if we want, but slicing is perfectly fine and idiomatic.
Actually, using `items.iter().skip(1)` or just indexing is fine.
Let's stick to the slice approach or iterator.

One minor point: `current_val` is cloned initially. When we push to `result`, we clone it again. That's fine since `T: Clone`.
We could optimize by moving `current_val` into the tuple when pushing, but since we need to keep it for comparison, cloning is necessary unless we use `Option` or similar. Given the constraints, cloning is expected.

Let's test mentally:
`items = &[1, 1, 2, 3, 3, 3]`
- `current_val = 1`, `count = 1`
- loop `1`: `*item == current_val` -> `count = 2`
- loop `2`: `*item != current_val` -> push `(1, 2)`, `current_val = 2`, `count = 1`
- loop `3`: `*item != current_val` -> push `(2, 1)`, `current_val = 3`, `count = 1`
- loop `4`: `*item == current_val` -> `count = 2`
- loop `5`: `*item == current_val` -> `count = 3`
- end loop -> push `(3, 3)`
Result: `[(1, 2), (2, 1), (3, 3)]` -> Correct.

Empty input: returns `Vec::new()` -> Correct.

What about `items = &[1, 2, 3]`?
- `current_val = 1`, `count = 1`
- loop `2`: push `(1, 1)`, `current_val = 2`, `count = 1`
- loop `3`: push `(2, 1)`, `current_val = 3`, `count = 1`
- end loop -> push `(3, 1)`
Result: `[(1, 1), (2, 1), (3, 1)]` -> Correct.

The code looks solid.

One thing: `items[1..]` might panic if `items` is empty, but we check `is_empty()` first. So it's safe.
Alternatively, we can use an iterator approach that handles empty gracefully without explicit check, but the explicit check is fine.

Let's consider using `peekable` iterator for a more idiomatic approach, though the slice approach is simpler.
Actually, the slice approach is very clear.

Wait, `items[1..]` creates a new slice reference, which is cheap.
I'll stick with it.

Let's write out the full `src/lib.rs` content.

```rust
pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)> {
    if items.is_empty() {
        return Vec::new();
    }

    let mut result = Vec::new();
    let mut current_val = items[0].clone();
    let mut count = 1;

    for item in &items[1..] {
        if *item == current_val {
            count += 1;
        } else {
            result.push((current_val.clone(), count));
            current_val = item.clone();
            count = 1;
        }
    }
    result.push((current_val, count));
    result
}
```

Is there any edge case? `T` implements `PartialEq` and `Clone`. The comparison `*item == current_val` works because `item` is `&T` and `current_val` is `T`. `PartialEq` is implemented for `&T` and `T` automatically if `T: PartialEq`. Actually, `*item == current_val` compares `T` with `T`. That's fine.

Wait, `*item == current_val` might require `T: PartialEq`. Yes, it's in the bounds.
Alternatively, `item == &current_val` works too. Both are fine.

One minor optimization: we can avoid cloning `current_val` when pushing by using `std::mem::replace` or just cloning since it's required by signature anyway. The problem says "Use `Clone` to copy the representative value into the result." So cloning is expected.

Let's double check the function signature matches exactly:
`pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)>`

Yes.

I will produce the code block for `src/lib.rs`.

One thing: sometimes `items[1..]` is considered slightly less idiomatic than `items.iter().skip(1)`, but both are fine. I'll stick to `&items[1..]` for simplicity, or use an iterator to be safe against any weirdness. Actually, `items.iter().skip(1)` is fine too. I'll keep `&items[1..]`.

Wait, `items[1..]` creates a slice, which is perfectly fine.

Let's consider if `items` contains references or values. The signature takes `&[T]`, so `items` is a slice of `T`. `items[0]` is `T`. `items[1..]` is `&[T]`. Iterating over it yields `&T`. So `*item` is `T`. `*item == current_val` compares `T` with `T`. This works.

All good.

I will write the solution.
One detail: `Vec::new()` vs `vec![]`. Both fine.
I'll format it nicely.

```rust
pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)> {
    if items.is_empty() {
        return Vec::new();
    }

    let mut result = Vec::new();
    let mut current_val = items[0].clone();
    let mut count = 1;

    for item in &items[1..] {
        if *item == current_val {
            count += 1;
        } else {
            result.push((current_val.clone(), count));
            current_val = item.clone();
            count = 1;
        }
    }
    result.push((current_val, count));
    result
}
```

This matches all requirements.
I'll output it as requested.
No extra prose.
Done. 
Let's verify one more time.
`group_consecutive(&[1, 1, 2, 3, 3, 3])` -> `vec![(1, 2), (2, 1), (3, 3)]`
`group_consecutive(&['a', 'a', 'b', 'a'])` -> `vec![('a', 2), ('b', 1), ('a', 1)]`
`group_consecutive::<i32>(&[])` -> `vec![]`
`group_consecutive(&[1, 2, 3])` -> `vec![(1, 1), (2, 1), (3, 1)]`
`group_consecutive(&[7, 7, 7])` -> `vec![(7, 3)]`

All examples match.
The constraints are satisfied.
Ready. 
Output matches request.
Proceeds. 
Self-Correction/Refinement during thought: 
Could use `items.iter()` to avoid slicing, but slicing is fine. 
Actually, `items[1..]` might be slightly less efficient if `items` is huge? No, slicing is O(1). 
It's perfectly fine.

One minor point: `*item == current_val` relies on `PartialEq` for `T`. The bound is `T: PartialEq + Clone`, so it's satisfied.
All good.
Output matches. 
Proceeds. 
```rust
pub fn group_consecutive<T: PartialEq + Clone>(items: &[T]) -> Vec<(T, usize)> {
    if items.is_empty() {
        return Vec::new();
    }

    let mut result = Vec::new();
    let mut current_val = items[0].clone();
    let mut count = 1;

    for item in &items[1..] {
        if *item == current_val {
            count += 1;
        } else {
            result.push((current_val.clone(), count));
            current_val = item.clone();
            count = 1;
        }
    }
    result.push((current_val, count));
    result
}
```
This is correct. I will wrap it in the requested format.
The prompt asks for `src/lib.rs`.
I will provide exactly that.
Done. 
Output generation. 
[Done]