【问题标题】:Random access over all pair-wise combinations of large list in Python随机访问 Python 中大列表的所有成对组合
【发布时间】:2023-03-03 11:55:01
【问题描述】:

背景:

我有一个包含 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 以在此列表中找到 ith 成对的数字组合,以及一种以某种方式动态计算此组合的方法,而无需参考到无法存储在内存中的 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 个组合。

问题

是否还有其他以这种方式运行的组合函数旨在处理庞大的数据集?如果没有,是否有一种好方法可以实现这种行为的内存节省算法?

【问题讨论】:

  • 二进制文件能满足您的需求吗?您可以使用 .seek() 命令对其进行索引。
  • 我对你需要对你的组合做什么感到有点困惑。您的标题表明您想要迭代它们,从 itertools.combinations 返回的迭代器似乎很理想(只是不要把它变成一个列表!)。但是,您的问题主体似乎需要以某种不清楚的方式随机访问组合。如果要迭代,则不需要随机访问。如果您确实需要随机访问,您可能会想出一种方法将您的组合索引转换为原始列表中的一对索引。
  • 这里肯定发生了 XY 问题。你到底想做什么?
  • 当然——尽管我发布的答案更快并且使用更少的内存。 /// 使用 itertools 生成器并将对直接写入文件;使用二进制格式,以便每个整数占用相同数量的空间(每个 4 个字节,每对 8 个字节)。以 pair_file 的形式打开您的二进制文件。当你想从集合中配对 N 时,执行 pair_file.seek(8*N) 然后从该点读取两个整数。
  • @jackskis,这完全没问题。基本整数远大于此(32 或 64 位)。

标签: python list combinations combinatorics large-data


【解决方案1】:

除了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()设计方式(即迭代它,而不是强迫它产生一个巨大的列表)?

【讨论】:

  • 我编辑了这个问题,以进一步完善我正在寻找的内容。
  • 谢谢!如果您想在恒定时间内“随机访问”,那么您需要为此编写自己的代码。 @Captain 已经草拟了一个开始;我会充实它,但现在不能抽出时间。不过,我打赌其他人可以;-)
  • 是的,我只是想知道是否有一些我可以利用的预先编写的库!感谢您的帮助!
  • 这是所需的数学运算:en.wikipedia.org/wiki/…
  • 它不会是固定时间的,但你可以像这样使用 islice:itertools.islice(it, 5, 7) 所以它将包含第 6 个和第 7 个元素。
【解决方案2】:

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

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 在 cmets 中建议的那样,获得与 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 )

【讨论】:

  • 请注意,这确实比这更棘手(请参阅您的 6 [3, 3] ( 4, 4 ) 输出了解原因)。
  • 是的,而且这个解决方案的行为不像 'itertools.combinations()' 那样
  • 要修复排序,您需要从排序的末尾开始计数并反转索引。使用n, m = comb(size - 1 - i),然后使用small[length - 1 - n]small[length - 1 - m]
  • 不,我想出了这个,但我确定我不是第一个。我会写点东西。它本质上与枚举矩阵的下三角部分相反。该公式是二次方程x^2-x-2k的根,由x*(x-1)/2作为求解下界时的三角数得出。
  • 这里是关于 SE 的参考。 math.stackexchange.com/questions/455511/…
【解决方案3】:

我从三角形排列开始,找到索引 rowcol 的列表成员的下标 k。然后我颠倒这个过程,从 k 导出 rowcol

对于 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]

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

【讨论】:

    【解决方案4】:

    所以你有 44906 个项目。但是请注意,如果您以与示例中相同的方式构建组合,则有 44905 个组合,large[0] 作为第一个数字。此外,ii <= 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...

    【讨论】:

      【解决方案5】:

      对于明确定义的创建对的顺序,第一个和第二个元素的索引应该与序列的 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 的公式。

      【讨论】:

        猜你喜欢
        • 2021-03-14
        • 1970-01-01
        • 1970-01-01
        • 2015-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多