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

Random access over all pair-wise combinations of large list in Python

背景:

我有一个包含 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) 将 return 元组列表如下:

[(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)' 会 return:

(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)

因此,与其必须执行 next() 10 次才能获得第 10 个组合,我希望能够通过一次调用检索第 10 次迭代 returned 的元组。

问题

是否有任何其他组合函数以这种方式设计用于处理庞大的数据集?如果没有,是否有实现这种行为的内存节省算法的好方法?

除了 itertools.combinations 不是 return 列表 - 它 return 是一个迭代器。这里:

>>> 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)

等等。它非常节省内存:每次调用只生成一对。

当然 可以编写一个 return 是 n'th 结果的函数,但是在打扰之前(这会更慢并且更多参与),你确定你不能只使用 设计 的方式使用 combinations() (即迭代它,而不是强迫它产生一个巨大的列表)?

所以你有 44906 件物品。但是请注意,如果您按照示例中构建组合的方式构建组合,那么将有 44905 个组合,其中 large[0] 作为第一个数字。此外,i <= 44905 的组合 i 看起来像 (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...

对于定义明确的创建对顺序,第一个和第二个元素的索引应与序列的 n 和长度相关。如果找到它们,您将能够实现常量时间性能,因为索引列表是 ​​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 的公式。

如果你想随机访问任何组合你可以使用这个函数来return叉积的相应下三角表示的索引

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 )

我从三角形排列开始,找到索引为 rowcol[= 的列表成员的下标 k 29=]。然后我逆转了这个过程,从 k.

推导出 rowcol

对于包含 N 项的列表 large,令

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]

这允许您访问组合列表的任何成员而无需生成该列表。