【问题标题】:How can I implement Python sets in another language (maybe C++)?如何用另一种语言(可能是 C++)实现 Python 集?
【发布时间】:2013-09-26 15:39:38
【问题描述】:

我想将我已经编写的一些 Python 代码翻译成 C++ 或其他快速语言,因为 Python 的速度不够快,无法完成我想做的事情。然而,有问题的代码滥用了 Python 集合的一些令人印象深刻的特性,特别是我在性能关键循环中发送垃圾邮件的平均 O(1) 成员资格测试,而且我不确定如何用另一种语言实现 Python 集合。

Python's Time Complexity Wiki Page 中,它指出集合的成员资格测试平均为 O(1),最坏情况下为 O(n)。我使用timeit 亲自对此进行了测试,并且惊讶于 Python 集执行成员资格测试的速度之快,即使 N 很大。我查看了this Stack Overflow answer 以了解 C++ 集在使用 find 操作以查看元素是否为给定集合的成员,它说它是 O(log(n))。

我假设 find 的时间复杂度是对数的,因为 C++ 标准库集是用某种二叉树实现的。我认为因为 Python 集具有平均 O(1) 成员资格测试和最坏情况 O(n),它们可能是用某种带有桶的关联数组实现的,它可以轻松查找一个元素并对其进行一些虚拟值测试这表明该元素不是集合的一部分。

问题是,我不想通过切换到另一种语言来减慢代码的任何部分(因为这是我试图首先解决的问题)所以我如何实现我自己的 Python 版本用另一种语言设置(特别是快速会员测试)?有人知道 Python 集是如何实现的吗?如果不知道,谁能给我任何一般性的提示来指出正确的方向?

我不是在寻找源代码,我只是在寻找可以帮助我入门的一般想法和链接。

我对@9​​87654323@ 进行了一些研究,我想我了解它们实现背后的基本思想,但我不确定它们的内存使用情况。如果 Python 集确实只是真正的关联数组,我怎样才能以最少的内存使用来实现它们?

附加说明:我要使用的相关集合最多包含 50,000 个元素,并且集合中的每个元素都在一个很大的范围内(例如 [-999999999, 999999999])。

【问题讨论】:

  • 如果您想在 C++ 中进行 O(1) 查找,请使用 std::unordered_set。但是,50k 个元素并不多,在标准 std::set 中进行查找应该需要少于 16 次比较。
  • 我绝对想要 O(1) 查找,因为我在一个循环中有多个查找。感谢 unordered_sets 的链接,我不知道 std 库有这个。这可能会为我省去很多麻烦。

标签: c++ python c arrays algorithm


【解决方案1】:

几点:正如已经指出的那样,您拥有std::setstd::unordered_set(后者仅在 C++11 中,但大多数编译器都有 多年来一直提供类似的扩展)。这 首先是由某种平衡树(通常是红黑 树),第二个作为 hash_table。哪个更快取决于 数据类型:第一个需要某种排序关系(例如 <如果是在类型上定义的,但你可以自己定义);这 第二个等价关系(例如==)和一个哈希 符合这种等价关系的函数。第一个是 O(lg n),第二个 O(1),如果你有一个很好的哈希函数。因此:

  • 如果顺序比较明显快于散列, std::set 实际上可能更快,至少对于“较小”的数据集, 其中“更小”取决于差异有多大——因为 字符串,例如,比较通常会在第一个 几个字符,而哈希码将查看每个 特点。在我做的一个实验中(很多年前),用字符串 30-50个字符,我发现盈亏平衡点在100000左右 元素。

  • 对于某些数据类型,只需找到一个好的散列函数即可 与类型兼容可能很困难。 Python 使用哈希表 它的集合,如果你用函数__hash__ 定义一个类型,它总是 返回 1,它会非常非常慢。编写一个好的哈希函数 并不总是很明显。

  • 最后,两者都是基于节点的容器,这意味着它们使用了很多 比例如更多的内存std::vector,地段很差。如果查找 是主要操作,您可能要考虑std::vector, 保持排序并使用std::lower_bound 进行查找。 根据类型的不同,这可能会导致显着的加速,并且 更少的内存使用。

【讨论】:

    【解决方案2】:
    1. O(1)O(log n) 之间的理论差异在实践中意义不大,尤其是在比较两种不同的语言时。对于n 的大多数实际值,log n 很小。每个实施的常数因素很容易变得更重要。
    2. C++11 现在有unordered_setunordered_map。即使你不能使用 C++11,也总是有 Boost 版本和 tr1 版本(后者被命名为 hash_* 而不是 unordered_*)。

    【讨论】:

    • 谢谢,我会考虑您的建议。我将首先尝试使用set,如果这还不够快,我将使用unordered_set。我想当我考虑它时,对于 50k 的 n,O(log(n)) 并不是那么大。
    • @ShashankGupta 在多年前我进行的一项测试中,盈亏平衡点(在std::set 和我的哈希表实现之间)大约是 100K 元素。然而,这在很大程度上取决于集合中的什么std::set 在比较次数上是 O(lg n); std::unordered_set 是 O(1),如果你有一个很好的哈希函数。根据数据的不同,计算一个好的散列可能需要数十次或更多次比较的时间。不上。这取决于数据。 (FWIW:Python 使用哈希表。如果你创建自己的类型,并给它一个不好的__hash__,它会很慢。)
    猜你喜欢
    • 2011-05-07
    • 1970-01-01
    • 2012-12-29
    • 2015-05-04
    • 1970-01-01
    • 1970-01-01
    • 2012-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多