rust 我能高效地从哈希集中弹出吗?

8zzbczxx  于 2023-02-08  发布在  其他
关注(0)|答案(3)|浏览(139)

我的算法需要通过删除一个元素来迭代地收缩一个集合,并在每次迭代中对删除的元素和收缩的集合做一些事情。

  • 我需要一个真正的快速查找集,而不仅仅是一个包含唯一元素的向量。
  • 元素的选择是任意的:算法的结果并不取决于访问的顺序,性能可能会随着选择的不同而有很大的变化,但是假设我想要最简单的代码,让集合自己选择它可以有效删除的元素。
  • 克隆元素的成本很低(实际上很可能是可复制的整数,但让我们在这里的示例中明确地说明克隆)。

顺便说一下,算法是the basic form of the Bron–Kerbosch algorithm。该算法的更智能版本工作更快(大多数情况下),因为它们不会让元素的选择是任意的,我想知道与优化弹出操作相比,这种努力的回报是多少。
Python集合中有一个pop成员,它可以很好地完成这个任务,在Scala和Go语言中,选择并删除散列集合中的“first”元素似乎可以很好地完成这个任务(其中“first”对应于迭代器),在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
}

与其他语言相比,这似乎是一个性能瓶颈,但即使是以一种看似幼稚的方式在Rust中进行这种迭代:复制序列,然后弹出sequence. I benchmarked some implementations of a pop-like function on the playground中的元素,但与原始方法相比,没有一种方法的性能更好。
一开始,我以为删除一个元素并不昂贵,但用iter().next()选择一个元素代价很高,但仔细检查后发现并非如此,至少与其他语言相比是这样()。
可以理解,使用retain并没有什么帮助:它总是迭代整个集合,还有别的选择吗
)仔细检查一下,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
tag5nh1u

tag5nh1u1#

我使用的集合是整数
不要使用HashSet;BTreeSet具有更好和更一致的性能。
对于N = 100000 ...

    • 一米三米一x**
sequenced : 3065.098µs
pop_1     : 2941.876µs
pop_2     : 2927.429µs
    • 一米四分一秒**
sequenced : 3091.454µs
pop_1     : 172547.080µs
pop_2     : 807182.085µs
wecizke3

wecizke32#

我想同样的建议也适用于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);

这意味着,如果您只需要收缩集合一次或几次(选择任意元素),或者如果集合的内容不能被廉价地克隆,那么这个答案就不适用。

nfg76nw0

nfg76nw03#

您的代码可以稍微简化:

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

如果你想删除HashSet中的所有元素,那么你应该使用drain迭代器--它非常高效。
Rust标准库中的HashSet速度不够快。请尝试用hashbrown机箱中的一个替换它。

相关问题