选择python数据结构以加速算法实现

时间:2016-08-10 03:53:59

标签: python algorithm performance optimization data-structures

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

我找到了一个算法(无法记住来源,道具的非特异性道歉),它利用了0到27号码的总子集数量少于那里的事实。是字典中的单词。

我做的第一件事就是循环遍历所有列表,找到构成它的整数的唯一子集,并将其索引为0到1之间的数字<&lt; 28。如下:

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 <28)-1的所有子集,因为不保证所提供的集合具有包含每个唯一子集的列表。

我想要的是,对于每个子集0到1&lt;&lt; 28(这次都是所有这些),是包含至多该子集的最长列表。这是杀死我的部分。在高级别,对于每个子集,它应该首先检查该子集是否包含在index_hash中。然后,它应该将散列中的条目的长度(如果它存在)与先前存储在当前散列中的当前子集的长度减去一个数字(这是内部循环27强)进行比较。其中最大的存储在外部循环的当前子集的新哈希中。现在的代码如下所示:

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),但我的实现抱着我。

2 个答案:

答案 0 :(得分:2)

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

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

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清除位的集合,而奇数行中的数据对应于具有在bit_index设置位。然后numpy.maximum调用执行该位的冒泡操作。最后,index_array[i]的每个单元格index_array代表最长列表的长度,其中元素是集合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及其补充表示的集合的子集,是我们想要的列表。

答案 1 :(得分:1)

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

例如,集合{0,2,3}将由2 0 + 2 2 + 2 3 = 13表示{4,5}表示为2 4 + 2 5 = 48

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