背景
我有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个组合。
问题
是否有任何其他组合函数以这种方式处理大型数据集?如果没有,是否有一种很好的方法来实现以这种方式运行的节省内存的算法?
答案 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的公式。