【问题标题】:Choosing python data structures to speed up algorithm implementation选择python数据结构加速算法实现
【发布时间】:2016-08-10 03:53:59
【问题描述】:

所以我得到了一大堆列表(大约 200k)。每个都包含数字 0 到 27 的子集。我想返回两个列表,其中它们的长度的乘积大于任何其他列表对的长度的乘积。还有另一个条件,即这些列表没有共同的数字。

我为此找到了一个算法(不记得来源,为道具的非特异性道歉),它利用了数字 0 到 27 的总子集比字典中的单词少的事实。

我做的第一件事是遍历所有列表,找到组成它的唯一整数子集,并将其索引为 0 到 1

def index_lists(lists):
    index_hash = {}
    for raw_list in lists:
        length = len(raw_list)

        if length > index_hash.get(index,{}).get("length"):
           index = find_index(raw_list)
           index_hash[index] = {"list": raw_list, "length": length}

    return index_hash

这给了我最长的列表和列表的长度,每个子集实际上包含在给定列表的集合中。当然,并非必须包含从 0 到 (1

然后我想要的是,对于每个子集 0 到 1

def at_most_hash(index_hash):
    most_hash = {}
    for i in xrange(1<<28):  # pretty sure this is a bad idea
        max_entry = index_hash.get(i)
        if max_entry:
           max_length = max_entry["length"]
           max_word = max_entry["list"]
        else:
           max_length = 0
           max_word = []
        for j in xrange(28):  #  again, probably not great
           subset_index = i & ~(1<<j) # gets us a pre-computed subset
           at_most_entry = most_hash.get(subset_index, {})
           at_most_length = at_most_entry.get("length",0)
           if at_most_length > max_length:
              max_length = at_most_length
              max_list = at_most_entry["list"]
        most_hash[i] = {"length": max_length, "list": max_list}
    return most_hash

这个循环显然需要好几次才能完成。我觉得我对 python 足够陌生,以至于我选择如何迭代和使用什么数据结构可能完全是灾难性的。更不用说试图填满字典的预期记忆问题了。是否有更好的结构或包用作数据结构?还是设置迭代的更好方法?或者也许我可以更稀疏地做到这一点?

算法的下一部分只是循环遍历我们给出的所有列表,并通过在 at_most_hash 中查找子集的 max_length 和互补子集的最大长度的乘积,取其中的最大值。

这里有什么建议吗?感谢您耐心地解决我冗长的问题,以及编写代码时不太体面的尝试。

理论上,这仍然是比单独使用列表集合更好的方法,因为这种方法大约是 o(200k^2) 而这个大约是 o(28*2^28 + 200k),但我的实现阻碍了我。

【问题讨论】:

  • 我认为这篇文章更适合 [code-review]。
  • 你的第二句话让我很困惑:“每个都包含数字 0 到 27。”你真的是说它们包含这些数字的一个子集吗?否则问题的其余部分毫无意义,因为每个列表都有相同的数字和相同的长度。
  • @JulienBernu:啊,那是一个单独的堆栈 站点吗?道歉。
  • @Blckknght:是的,我的意思是子集。我会编辑。
  • @AsinglePANCAKE 不用担心。是的:codereview.stackexchange.com

标签: python algorithm performance optimization data-structures


【解决方案1】:

鉴于您的索引只是整数,您可以通过使用列表而不是字典来节省一些时间和空间。我会更进一步,引入NumPy 数组。它们提供紧凑的存储表示和高效的操作,让您可以在 C 中隐式执行重复性工作,从而绕过大量的解释器开销。

我们首先构建一个 NumPy 数组,而不是index_hash,其中index_array[i] 是最长列表的长度,其元素集由i 表示,如果没有这样的列表,则为0

import numpy

index_array = numpy.zeros(1<<28, dtype=int)  # We could probably get away with dtype=int8.
for raw_list in lists:
    i = find_index(raw_list)
    index_array[i] = max(index_array[i], len(raw_list))

然后我们使用 NumPy 操作在 C 中而不是解释 Python 中增加长度。事情可能会从这里变得混乱:

for bit_index in xrange(28):
    index_array = index_array.reshape([1<<(28-bit_index), 1<<bit_index])
    numpy.maximum(index_array[::2], index_array[1::2], out=index_array[1::2])

index_array = index_array.reshape([1<<28])

每个reshape 调用都会获取数组的新视图,其中偶数行中的数据对应于位为bit_index 的集合,而奇数行中的数据对应于位为@987654330 的集合@ 放。然后numpy.maximum 调用对该位执行冒泡操作。最后,index_array 的每个单元格index_array[i] 代表最长列表的长度,其元素是集合i 的子集。

然后我们计算互补索引处的长度乘积:

products = index_array * index_array[::-1]  # We'd probably have to adjust this part
                                            # if we picked dtype=int8 earlier.

找出最好的产品在哪里:

best_product_index = products.argmax()

其中元素是best_product_index 表示的集合的子集及其补集的最长列表就是我们想要的列表。

【讨论】:

  • 天啊……速度增益简直太疯狂了。打得好,先生。
  • "每个 reshape 调用都会获取数组的新视图,其中偶数行中的数据对应于 bit_index 位清除的集合,奇数行中的数据对应于位为bit_index set." 这里的“清除”和“设置”是什么意思?
  • @AsinglePANCAKE:“clear”表示该位为 0,“set”表示该位为 1。
  • 有没有一种很好的方法来携带哪个列表产生了给定的最大值。我可以在 numpy 数组旁边构造一个 word_array 而不会有太多麻烦,但是所有基于取最大值的重塑,似乎很难将产生最大值的列表拉到一边。
  • @AsinglePANCAKE:您可能可以通过使用numpy.argmax 而不是numpy.maximum 来选择最大值的索引而不是最大值本身,然后使用索引来冒泡列表和长度在一起,但我想我可能会在拥有best_product_index 后通过原始列表进行暴力搜索以找到我们想要的列表。
【解决方案2】:

这对于评论来说有点太长了,所以我会发布它作为答案。将子集索引为整数的一种更直接的方法是使用“位集”,二进制表示中的每个位都对应于其中一个数字。

例如,集合 {0,2,3} 将表示为 20 + 22 + 23 = 13 {4,5} 将表示为 24 + 25 = 48

这将允许您使用简单的列表而不是字典和 Python 的通用散列函数。

【讨论】:

  • 这就是它们的索引方式。我放弃了我做的部分,因为我的问题的症结在于索引后的世界。我没有意识到散列这些是不好的......
  • 阅读你的算法的解释,在大 O 意义上似乎没有任何明显的错误。由于我们没有尝试优化常量因素,我建议使用分析器找出您的瓶颈。
  • 感谢阅读和评论。有没有一种方法来分析不需要脚本实际完成以对正在发生的事情进行运行分析的代码?除了在各处放置时间戳......
  • 试试stackoverflow.com/questions/582336/…。 AFAIK,如果你在中途杀死你的脚本,它应该仍然有效。如果没有,那么在您的情况下,您可以暂时减小 for i in xrange(1&lt;&lt;28): 循环的大小以获得类似的效果。
猜你喜欢
  • 2013-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多