July 21, 20256 min

How removing duplicates from a Vec<T> where T: Eq + Hash?

m
mayo

Efficient Approaches

When T implements Eq + Hash (for equality checks and hashing), the optimal methods are:

Vec<1,2,2,3,3,3> deduplication [1, 2, 2, 3, 3, 3] HashSet retain [1, 2, 3] order preserved O(n) time · O(n) space sort() + dedup() [1, 2, 3] order changed O(n log n) time · O(1) space

1. Using HashSet (Preserves Order)

Steps:

  1. Iterate through the Vec.
  2. Track seen elements with a HashSet.
  3. Collect only unseen elements.

Code:

use std::collections::HashSet;

fn dedup_ordered<T: Eq + std::hash::Hash + Clone>(vec: &mut Vec<T>) {
    let mut seen = HashSet::new();
    vec.retain(|x| seen.insert(x.clone()));
}

Example:

let mut vec = vec![1, 2, 2, 3, 3, 3];
dedup_ordered(&mut vec);
assert_eq!(vec, [1, 2, 3]); // Order preserved

The trick is that HashSet::insert returns false when the value was already present, which is exactly the answer retain wants:

retain(|x| seen.insert(x.clone())) over [1, 2, 2, 3] x = 1 insert → true keep seen = {1} x = 2 insert → true keep seen = {1, 2} x = 2 again insert → false drop seen = {1, 2} x = 3 insert → true keep seen = {1, 2, 3} One pass over the Vec, one hash lookup per element, and the first occurrence always wins — hence stable order.

Performance:

  • Time: O(n) (average case, assuming good hash distribution).
  • Space: O(n) (for the HashSet).

2. Sort + Dedup (Destroys Order)

Steps:

  1. Sort the Vec (groups duplicates together).
  2. Remove consecutive duplicates with dedup().

Code:

fn dedup_unordered<T: Ord>(vec: &mut Vec<T>) {
    vec.sort();      // O(n log n)
    vec.dedup();     // O(n)
}

Example:

let mut vec = vec![3, 2, 2, 1, 3];
dedup_unordered(&mut vec);
assert_eq!(vec, [1, 2, 3]); // Order changed

Performance:

  • Time: O(n log n) (dominated by sorting).
  • Space: O(1) (in-place, no extra allocations).

Comparison

Method Time Complexity Space Complexity Preserves Order? Use Case
HashSet O(n) O(n) ✅ Yes Order matters, no sorting allowed.
Sort + Dedup O(n log n) O(1) ❌ No Order irrelevant, memory-constrained.

Key Takeaways

Use HashSet if:

  • Order must be preserved.
  • You can tolerate O(n) space.

Use Sort + Dedup if:

  • Order doesn't matter.
  • Memory is tight (e.g., embedded systems).

Alternatives:

  • For no_std environments, use a BTreeSet (slower but avoids hashing).
  • Use itertools::unique for iterator-based deduplication.

Try This: What happens if T is Clone but not Hash?

Answer: Use Vec::dedup_by with a custom equality check (no hashing).