【问题标题】:Itertools combinations, ¿How to make it faster?Itertools 组合,¿如何使其更快?
【发布时间】:2021-09-18 15:49:16
【问题描述】:

我正在编写这个程序,它需要 54 (num1) 个数字并将它们放在一个列表中。然后它取其中的 16 (num2) 个数字并形成一个列表,其中包含从 "num1"c"num2" 的所有可能组合中选择的 16 个数字的列表。然后它会获取这些列表并生成 4x4 数组。

我的代码可以运行,但是运行 54 个数字来获取我想要的所有数组需要很长时间。我知道这一点是因为我已经使用 20 到 40 个数字测试了代码并对其进行了计时。

20 numbers =  0.000055 minutes
30 numbers =  0.045088 minutes
40 numbers =  17.46944 minutes

使用我得到的所有 20 点测试数据,我建立了一个数学模型来预测运行 54 个数字需要多长时间,我得到 1740 分钟 = 29 小时。这已经是对预测 38 小时的代码 v1 和实际上使我的机器崩溃的 v0 的改进。

我正在与您联系,以尝试使此运行更快。该程序甚至不是 RAM 密集型的。我有 8GB 的​​ RAM 和核心 i7 处理器,它根本不会减慢我的机器速度。与我以前的版本相比,它实际上运行非常流畅,我的电脑甚至崩溃了几次。

你们觉得有办法吗?我目前正在抽样以减少处理时间,但如果可能的话,我宁愿不抽样。我什至没有打印数组以减少处理时间,我只是打印一个计数器来查看我生成了多少组合。

这是代码:

import numpy as np
import itertools
from itertools import combinations
from itertools import islice
from random import sample

num1 = 30 #ideally this is 54, just using 30 now so you can test it.
num2 = 16
steps = 1454226 #represents 1% of "num1"c"num2" just to reduce processing time for testing.

nums=list()
for i in range(1,num1+1):
    nums.append(i)
#print ("nums: ", nums) #Just to ensure that I am indeed using numbers from 1 to num1

vun=list()
tabl=list()
counter = 0
combin = islice(itertools.combinations(nums, num2),0,None,steps)
for i in set(combin):
    vun.append(sample(i,num2)) 
    counter = counter + 1
    p1=i[0];p2=i[1];p3=i[2];p4=i[3];p5=i[4];p6=i[5];p7=i[6];p8=i[7];p9=i[8]
    p10=i[9];p11=i[10];p12=i[11];p13=i[12];p14=i[13];p15=i[14];p16=i[15]
    tun = np.array ([(p1,p2,p3,p4),(p5,p6,p7,p8),(p9,p10,p11,p12),(p13,p14,p15,p16)])
    tabl.append(tun)
    # print ("TABL:" ,tabl)
# print ("vun: ", vun)
print ("combinations:",counter)

我用这段代码得到的输出是:

combinations: 101

理想情况下,该数字应为 2.109492366(10)¹³ 或至少 1%。只要它运行 54x16 并且不需要 29 小时。

【问题讨论】:

  • 通过将 54 个数字放入一个列表中,然后取其中的 16 个等,您想达到什么目标?
  • @chepner 不是指数级的。只需 O(n^k)。
  • 在 RAM 中存储大量数据。说 “该程序甚至不是 RAM 密集型的。”
  • 21 万亿个长度为 16 的列表涉及超过 250 万亿个数字。如果您想在普通计算机上处​​理这么多数据,那么一天多一点就完成了。
  • 仍然想知道代码的用途。我害怕对学习真正的用例感到失望......

标签: python performance combinations itertools


【解决方案1】:

主要的低效来自于生成所有组合 (itertools.combinations(nums, num2)),然后将大部分组合丢弃。

另一种方法是随机生成组合,确保没有重复。

import itertools
import random

def random_combination(iterable, r):
    "Random selection from itertools.combinations(iterable, r)"
    pool = tuple(iterable)
    n = len(pool)
    indices = sorted(random.sample(range(n), r))
    return tuple(pool[i] for i in indices)
    
items = list(range(1, 55))

samples = set()

while len(samples) < 100_000:
    sample = random_combination(items, 16)
    samples.add(sample)

for sample in samples:
    board = list(sample)
    random.shuffle(board)
    board = [board[0:4], board[4: 8], board[8: 12], board[12: 16]]

print("done")

这使用来自this question 的答案中的random_combination 函数,而该函数又来自itertools 文档。

代码在大约 10 秒内生成 100,000 个唯一的 4x4 样本,至少在我的机器上是这样。

几点说明:

  • 每个样本都是一个元组,条目是排序的;这意味着我们可以将它们存储在一个集合中并避免重复。
  • 由于第一点,我们在创建 4x4 板之前对每个样本进行洗牌;后面的代码对这些板没有任何作用,但我想包含它们以了解时间。
  • 如果您要对大部分空间进行采样,可能会出现大量哈希冲突,但无论如何这都不可行,因为涉及的数据量很大(见下文)。

我认为您在这里尝试实现的目标有些混乱。

54C16 = 2.1 x 10^13 ... 为所有这些点存储 16 个 8 位整数需要 2.7 x 10^15 位,即 337.5 TB。这超出了本地磁盘所能存储的范围。

因此,即使覆盖 1% 的空间也将占用超过 3TB 的空间……也许可以一键存储在磁盘上。您在问题中暗示您想覆盖这部分空间。显然,在 8GB 的​​ RAM 中不会发生这种情况。

【讨论】:

  • 写完这篇文章后,我意识到这实际上只是@pi-marillion 答案第二部分的扩展。
  • 先生。这绝对是正确的答案。我真的很感谢你的帮助。你是如何实现它的,真是太棒了。或者也许对其他专家来说并不那么神奇,但作为一个编码新手,这对我来说看起来很神奇。好极了!我也很高兴我所取得的成就和学到的东西。我将尝试与您所写内容类似的内容,并受到@pi-marillion 所写内容的启发。当然,我永远不会像你那样写得好。谢谢!
【解决方案2】:

仅仅计算组合的数量是微不足道的,因为它是just a formula

import math

math.comb(30, 16)
# 145422675

math.comb(54, 16)
# 21094923659355

问题在于,在我的机器上存储 16 of 30 案例的结果需要大约 64 GB 的 RAM。您可能但可能没有像我一样坐在那里的那么多 RAM。 54 个案例中的 16 个需要大约 9.3 PB 的 RAM,这是现代架构不支持的。

您将需要采取以下两种方法之一:

  1. 限制为 16 in 30 case,并且不要将任何结果存储到 vuntabl

    优点:在我的测试中可以在

    缺点:根本不适用于 16 in 54 的情况,不需要额外的处理

  2. 改为使用Monte Carlo simulation:生成随机组合,直至达到一些较大但可达到的样本数,然后对它们进行数学运算。

    优点:速度快,支持 16 of 30 和 16 of 54,可能具有相同的时间性能

    缺点:根据随机种子,结果会有一些随机变化,应进行统计处理以获得有效性的置信区间。

    注意:用于置信区间的公式取决于您打算对这些数字进行哪些实际数学运算,但如果您只是在寻找估计值,则平均值是一个不错的起点。

我强烈建议选项 (2),蒙特卡洛模拟

【讨论】:

  • @don'ttalkjustcode 感谢您的建议。我一直在使用 RHEL,它有 Python 3.6 而不是 3.8,所以上面没有 math.comb。不过我家的盒子有 3.8,所以math.comb FTW
  • 感谢您的回复。我认为整个事情存在误解。我的机器没有崩溃,它没有尝试使用 64GB 的 RAM,甚至没有 9.3PB 的 RAM(这很有趣)。目前它正在运行 num1=54 的情况,如果我的预测是正确的,它应该在明天结束,为我提供与我使用的步长相同的 101 种组合。以 num1=30 和 30c16 的 1% 的步长运行此代码需要 0.045 分钟(27 秒)。但是以 num1=54 和 54c16 的 1% 的步长运行它需要更长的时间,而不会导致我的机器或你的机器或任何尝试此操作的人崩溃。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-08
相关资源
最近更新 更多