Skip to content

Vec::dedup_by

Vec methods · Collections

Level: reference · for working programmers

One line: dedup with your own definition of "the same".

pub fn dedup_by<F>(&mut self, same_bucket: F)
where
    F: FnMut(&mut T, &mut T) -> bool,

Stable since 1.16.0.

Still consecutive-only. The closure replaces ==, nothing else changes.

The argument order is the part to get right. The closure is called as (a, b) where a is the later element and b the one already kept — and a is what gets removed when you return true. Reading it as (previous, current) inverts any asymmetric comparison.

Both arguments are &mut, which is what makes merging possible: the survivor can absorb what the duplicate carried. Summing counts across a run of equal keys is one call.

It is also the only member of the family that can express approximate equality — "within 0.01 of the last one kept" is not an equivalence relation and has no key function, so neither dedup nor dedup_by_key can say it.

Note that the comparison is against the last kept element, not the immediately preceding one in the original vector, which is what makes the approximate case behave sensibly.

Example

vec_dedup_by.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.

fn main() {
    // The closure decides what "the same" means. It is still consecutive-only.
    let mut v = vec!["foo", "FOO", "bar", "Bar", "baz"];
    v.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
    println!("{v:?}");

    // Argument order matters and is easy to get backwards: the closure is
    // called as (current, previous) — `a` is the LATER element, `b` the one
    // already kept. `a` is what gets removed when you return true.
    let mut order = vec![];
    let mut v = vec![1, 2, 3];
    v.dedup_by(|a, b| { order.push((*a, *b)); false });
    println!("closure saw (a, b) pairs: {order:?}");

    // Both are &mut, so the survivor can absorb what the duplicate carried.
    // Here consecutive equal keys are merged by summing their counts.
    let mut counts = vec![("a", 1), ("a", 2), ("b", 5), ("b", 1), ("b", 1)];
    counts.dedup_by(|a, b| {
        if a.0 == b.0 { b.1 += a.1; true } else { false }
    });
    println!("merged runs: {counts:?}");

    // "Nearly equal" is a job only dedup_by can do.
    let mut samples: Vec<f64> = vec![1.0, 1.001, 1.5, 1.502, 3.0];
    samples.dedup_by(|a, b| (*a - *b).abs() < 0.01);
    println!("within 0.01 of the last kept: {samples:?}");

    // dedup() is exactly dedup_by(|a, b| a == b).
    let mut x = vec![1, 1, 2, 2, 3];
    let mut y = x.clone();
    x.dedup();
    y.dedup_by(|a, b| a == b);
    println!("{x:?} == {y:?}: {}", x == y);
}

Verified output of vec_dedup_by.rs — regenerated by tools/run_examples.py, never hand-typed.

["foo", "bar", "baz"]
closure saw (a, b) pairs: [(2, 1), (3, 2)]
merged runs: [("a", 3), ("b", 7)]
within 0.01 of the last kept: [1.0, 1.5, 3.0]
[1, 2, 3] == [1, 2, 3]: true

See also

Vec::dedup_by in the standard library ↗