【发布时间】:2019-02-15 15:41:01
【问题描述】:
我的算法需要通过移除一个元素来迭代地收缩一个集合,并在每次迭代中对移除的元素和收缩集做一些事情。并且:
- 我需要一个能够快速查找的真正集合,而不仅仅是包含独特元素的向量。
- 元素的选择是任意的:算法的结果不依赖于访问的顺序。性能可能因选择而有很大差异,但假设我想要最简单的代码,并让集合本身来选择它可以有效删除的元素。
- 顺便说一下,算法是the basic form of the Bron–Kerbosch algorithm。该算法的更智能版本运行速度更快(主要是),因为它们不会随意选择元素,我想了解这种努力有多少回报。
Python 集有一个 pop 成员,几乎可以做到这一点。在 Scala 和 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 避免这种情况可以理解并没有帮助:它总是迭代整个集合。有其他选择吗?
(*) 仔细检查后,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
【问题讨论】:
-
请注意,我使用的集合是整数,所以我没有考虑内存管理 - 我想根本不需要克隆来将元素移出集合。
-
@LukasKalbertodt 我试着解释一下
-
在操场上运行基准测试是一个非常糟糕的主意。这是一个供所有人使用的单一共享 EC2 实例。有很多原因表明其中的任何数字都是可疑的。
-
@Shepmaster 这就是为什么需要一些样本;然而,根据我的经验,结果是相当一致的
-
“与其他语言相比,这是一个性能瓶颈。”你没有证明这一点。我也对此表示怀疑。