随机访问Python中大型列表的所有成对组合

时间:2016-07-06 21:05:22

标签: python list combinations combinatorics large-data

背景

我有44906个项目列表:large = [1, 60, 17, ...]。我还有一台内存有限(8GB)的个人电脑,运行Ubuntu 14.04.4 LTS。

目标:

我需要以节省内存的方式找到large的所有成对组合,而不事先填写包含所有组合的列表。

问题&我到目前为止所做的一切:

当我使用itertools.combinations(large, 2)并尝试将其分配到列表时,我的内存会立即填满,而且性能会非常慢。这样做的原因是成对组合的数量类似于n*(n-1)/2,其中n是列表中元素的数量。

n=44906的组合数量为44906*44905/2 = 1008251965。包含这么多条目的列表太大而无法存储在内存中。我希望能够设计一个函数,以便我可以插入一个数字i来查找此列表中i成对的数字组合,以及以某种方式动态计算此数字的方法组合,没有引用无法存储在内存中的1008251965元素列表。

我正在尝试做的一个例子:

假设我有一个数组small = [1,2,3,4,5]

在我有代码的配置中,itertools.combinations(small, 2)将返回一个元组列表:

[(1, 2), # 1st entry
 (1, 3), # 2nd entry
 (1, 4), # 3rd entry
 (1, 5), # 4th entry
 (2, 3), # 5th entry
 (2, 4), # 6th entry 
 (2, 5), # 7th entry
 (3, 4), # 8th entry
 (3, 5), # 9th entry
 (4, 5)] # 10th entry

调用这样的函数:`find_pair(10)'将返回:

(4, 5)

,给出了可能数组中的第10个条目,但没有预先计算整个组合爆炸。

问题是,我需要能够进入组合的中间,而不是每次都从头开始,这就像迭代器一样:

>>> from itertools import combinations
>>> it = combinations([1, 2, 3, 4, 5], 2)
>>> next(it)
(1, 2)
>>> next(it)
(1, 3)
>>> next(it)
(1, 4)
>>> next(it)
(1, 5)

因此,我希望能够通过一次调用来检索第10次迭代返回的元组,而不必执行next()10次以获得第10个组合。

问题

是否有任何其他组合函数以这种方式处理大型数据集?如果没有,是否有一种很好的方法来实现以这种方式运行的节省内存的算法?

5 个答案:

答案 0 :(得分:6)

itertools.combinations不返回列表外 - 它返回一个迭代器。这里:

>>> from itertools import combinations
>>> it = combinations([1, 2, 3, 4, 5], 2)
>>> next(it)
(1, 2)
>>> next(it)
(1, 3)
>>> next(it)
(1, 4)
>>> next(it)
(1, 5)
>>> next(it)
(2, 3)
>>> next(it)
(2, 4)

等等。它的内存效率非常高:每次调用只生成一对。

当然 可以编写一个返回n'th结果的函数,但在烦恼之前(这将会更慢,更复杂),你是否确定你可以只是使用combinations() 设计的方式(即迭代它,而不是强制它产生一个巨大的列表)?

答案 1 :(得分:4)

如果您想随机访问任何组合,您可以使用此函数返回交叉产品的相应下三角形表示的索引

def comb(k):         
        row=int((math.sqrt(1+8*k)+1)/2)    
        column=int(k-(row-1)*(row)/2)  
        return [row,column]

使用小数组例如

small = [1,2,3,4,5]
length = len(small)
size = int(length * (length-1)/2)
for i in range(size):
    [n,m] = comb(i)
    print(i,[n,m],"(",small[n],",",small[m],")")

将给出

0 [1, 0] ( 2 , 1 )
1 [2, 0] ( 3 , 1 )
2 [2, 1] ( 3 , 2 )
3 [3, 0] ( 4 , 1 )
4 [3, 1] ( 4 , 2 )
5 [3, 2] ( 4 , 3 )
6 [4, 0] ( 5 , 1 )
7 [4, 1] ( 5 , 2 )
8 [4, 2] ( 5 , 3 )
9 [4, 3] ( 5 , 4 )

显然,如果您的访问方法符合其他方法将更加实用。

另请注意,comb函数与问题的大小无关。

正如@Blckknght在评论中所建议的那样获得与itertools版本相同的顺序更改为

for i in range(size):
        [n,m] = comb(size-1-i) 
        print(i,[n,m],"(",small[length-1-n],",",small[length-1-m],")")  


0 [4, 3] ( 1 , 2 )
1 [4, 2] ( 1 , 3 )
2 [4, 1] ( 1 , 4 )
3 [4, 0] ( 1 , 5 )
4 [3, 2] ( 2 , 3 )
5 [3, 1] ( 2 , 4 )
6 [3, 0] ( 2 , 5 )
7 [2, 1] ( 3 , 4 )
8 [2, 0] ( 3 , 5 )
9 [1, 0] ( 4 , 5 )

答案 2 :(得分:3)

我从这个三角形排列开始,找到索引为 col 的列表成员的下标 k 。然后我改变了这个过程,从 k 派生 col

对于列表 N 项目,请

b = 2*N - 1

现在,要在列表中获得 k 组合...

row = (b - math.sqrt(b*b - 8*k)) // 2
col = k - (2*N - row + 1)*row / 2
kth_pair = large[row][col]

这使您可以访问组合列表的任何成员,而无需生成该列表。

答案 3 :(得分:1)

所以你有44906项。但请注意,如果您按照在示例中构建组合的方式构建组合,那么将有44905个组合作为第一个数字large[0]。此外,i的组合i <= 44905看起来像(large[0], large[i])

对于44905 < i <= 89809,它看起来像(large[1],large[i-44904])

如果我没有弄错的话,这种模式应该继续使用(large[j],large[i-(exclusive lower bound for j)+1])之类的东西。你可以检查我的数学,但我很确定它是对的。无论如何,你可以迭代找到这些下界(所以对于j = 0,它是0,对于j = 1,它是44905等)迭代应该很容易,因为你只需要添加下一个降序数:44905,44905 + 44904, 44905 + 44904 + 44903 ...

答案 4 :(得分:1)

对于明确定义的创建对的顺序,第一和第二元素的索引应该与序列的n和长度相关。如果你能找到它们,你将能够实现const-time性能,因为索引列表是O(1)操作。

伪代码看起来像这样:

def find_nth_pair(seq, n):
    idx1 = f1(n, len(seq))  # some formula of n and len(seq)
    idx2 = f2(n, len(seq))  # some formula of n and len(seq)
    return (seq[idx1], seq[idx2])

您只需要找到idx1和idx2的公式。