我可以有效地从HashSet中弹出吗?

时间:2019-02-15 15:41:01

标签: rust hashset

我的算法需要通过删除元素来迭代缩小集合,并在每次迭代中对删除的元素和缩小集合进行处理。并且:

  • 我需要一个具有快速查找功能的真正集合,而不仅仅是一个包含唯一元素的向量。
  • 元素的选择是任意的:算法的结果不取决于访问的顺序。选择后,性能可能会有很大的不同,但是,我想说最简单的代码,然后将其交由集合本身来选择可以有效删除的元素。
  • 顺便说一句,我的算法是the basic form of the Bron–Kerbosch algorithm。该算法的智能版本工作速度更快(大部分情况下),因为它们不会使元素选择随心所欲,我想了解这种努力能带来多少回报。

Python集具有一个pop成员,几乎可以做到这一点。在Scala and Go中,选择并删除哈希集的“第一个”元素似乎很好(其中“第一个”对应于迭代器)。在Rust中,类似于:

// split off an arbitrary element from a (non-empty) set
pub fn pop<T>(set: &mut HashSet<T>) -> T
where
    T: Eq + Clone + std::hash::Hash,
{
    let elt = set.iter().next().cloned().unwrap();
    set.remove(&elt);
    elt
}

与其他语言相比,这似乎是性能瓶颈。我benchmarked some implementations of a pop-like function on the playground,但没有一个表现良好。显然,删除一个元素并不昂贵,但是选择一个元素是:iter().next()花费了一笔财富(*)。用retain避免这样做是无济于事的:它总是迭代整个集合。有其他选择吗?

通过仔细检查,PS iter().next()相当便宜,到目前为止,微基准测试是可以信赖的。 Separate microbenchmarks说从集合中选择任意元素的成本(在我的系统中以纳秒为单位):

| Type of set      | Number of elements in set instance
|                  | 100 | 10,000 | 1,000,000
| Rust HashSet     |   2 |      2 |         2
| Rust BTreeSet    |  11 |     12 |        13
| Go map[]struct{} |  27 |     31 |        94
| Python set       | 125 |    125 |       125

3 个答案:

答案 0 :(得分:3)

  

我正在使用的集合具有整数

请勿使用HashSetBTreeSet具有更好,更一致的性能。

对于N = 100000 ...

BTreeSet

sequenced : 3065.098µs
pop_1     : 2941.876µs
pop_2     : 2927.429µs

HashSet

sequenced : 3091.454µs
pop_1     : 172547.080µs
pop_2     : 807182.085µs

答案 1 :(得分:2)

我猜想与Can I randomly sample from a HashSet efficiently?中的建议相同:如"sequenced" solution in the benchmark中所示,将集合复制为向量只是为了对其进行迭代:

let seq: Vec<u32> = set.iter().cloned().collect();
for elt in seq {
    set.remove(&elt);

这意味着如果您只需要将集合收缩一次(选择任意元素)一次或几次,或者无法廉价地复制集合内容,则此答案不适用。

答案 2 :(得分:0)

您的代码可以简化一点:

let elt = set.iter().next().cloned().unwrap();
set.take(&elt).unwrap()

如果要删除HashSet中的所有元素,则应使用drain迭代器-它非常有效。

Rust标准库中的

HashSet并不是那么快。尝试用hashbrown箱子中的一个替换它。