16

Is there a type that preserves insertion order (think Vec) but only tracks unique values (think HashSet)? I want to avoid using Vec because I would first need to check if the value exists in it before insertion.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
tshepang
  • 12,111
  • 21
  • 91
  • 136
  • 1
    Did you see [linked-hash-map](https://crates.io/crates/linked-hash-map) crate? Maybe you can create a wrap with `V = ()`. – malbarbo May 31 '16 at 15:58
  • 1
    Essentially a duplicate of http://stackoverflow.com/q/30243100/155423 as sets are just maps without a value. – Shepmaster May 31 '16 at 15:58
  • If the only change you need is adding elements and the elements are copyable, you could simply use both a set and a vector, using the set for the duplicate checks and the vector to store the elements in-order. – CodesInChaos Jun 01 '16 at 08:27
  • @CodesInChaos can you show what the code would look like – tshepang Jun 01 '16 at 22:59
  • Which operation will be more used? Insert or remove? – malbarbo Jun 01 '16 at 23:14
  • About equal @malbarbo – tshepang Jun 01 '16 at 23:16
  • @Tshepang The Rust equivalent of [this C# code](https://gist.github.com/anonymous/cf8c67145f7d508aae123d41de59fa12). For Copy types this should have a direct equivalent in Rust. But since you clarified that you don't just need to add elements but also remove them, this approach won't work for you. – CodesInChaos Jun 02 '16 at 06:53

4 Answers4

12

linked_hash_set crate is now available. It's based on the linked-hash-map crate mirroring the std HashSet API as closely as possible.

extern crate linked_hash_set;
use linked_hash_set::LinkedHashSet;

let mut set = LinkedHashSet::new();
set.insert(234);
set.insert(123);
set.insert(345);
set.insert(123);

assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![234, 345, 123]);
Alex Butler
  • 306
  • 3
  • 6
  • I was disappointed to see it didn't implement retain() and I can't derive Deserialize for it. Just FYI. – Andrew Mackenzie Apr 15 '22 at 15:56
  • `linked-hash-map` does not implement `retain` which would be the first step for that. For serialization the optional "serde" dependency/feature is available. – Alex Butler Jun 11 '22 at 10:50
  • It is worth noting that when removal is not a frequently expected operation, [IndexSet](https://docs.rs/indexmap/latest/indexmap/set/struct.IndexSet.html) is a more appropriate choice giving better performance. – nirvana-msu Jun 24 '22 at 16:24
5

Updating answers since linked-hash-map is in maintenance mode (see github commit)


The most popular github crates in September 2021:

  • indexmap which provide direct implementation of IndexSet.
  • array_tool which provide helper to build a vector set data structure with few lines of code.
tshepang
  • 12,111
  • 21
  • 91
  • 136
arthurlm
  • 398
  • 2
  • 11
4

The linked-hash-map crate provides a hash map that holds key-value insertion order. We can create a set wrapper for this hash map using () as values (std::collections::HashSet is implemented this way):

extern crate linked_hash_map;

use linked_hash_map::*;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash};
use std::borrow::Borrow;

fn main() {
    let mut s = LinkedHashSet::new();
    s.insert(5);
    s.insert(3);
    s.insert(7);
    s.insert(1);
    assert_eq!(vec![5, 3, 7, 1], s.iter().cloned().collect::<Vec<_>>());
    s.remove(&7);
    assert_eq!(vec![5, 3, 1], s.iter().cloned().collect::<Vec<_>>());
    s.remove(&5);
    assert_eq!(vec![3, 1], s.iter().cloned().collect::<Vec<_>>());
}

pub struct LinkedHashSet<K, S = RandomState>(LinkedHashMap<K, (), S>);

impl<K: Hash + Eq> LinkedHashSet<K> {
    pub fn new() -> Self {
        LinkedHashSet(LinkedHashMap::new())
    }
}

impl<K: Hash + Eq, S: BuildHasher> LinkedHashSet<K, S> {
    pub fn insert(&mut self, k: K) -> Option<()> {
        self.0.insert(k, ())
    }

    pub fn contains<Q: ?Sized>(&self, k: &Q) -> bool
        where K: Borrow<Q>,
              Q: Eq + Hash
    {
        self.0.contains_key(k)
    }

    pub fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<()>
        where K: Borrow<Q>,
              Q: Eq + Hash
    {
        self.0.remove(k)
    }

    pub fn iter(&self) -> Keys<K, ()> {
        self.0.keys()
    }
}

You can implement other methods. See LinkedHashMap docs.

malbarbo
  • 10,717
  • 1
  • 42
  • 57
0

RiteLinked provides more up to date versions of LinkedHashMap & LinkedHashSet in Rust. You can easily use it on std or no_std environment. You can use it instead of linked-hash-map and linked_hash_set.

PsiACE
  • 106
  • 4