【问题标题】:Is there a way to change this nested loop into a recursive loop?有没有办法将此嵌套循环更改为递归循环?
【发布时间】:2019-08-22 10:47:42
【问题描述】:

我正在就以下问题寻求帮助。我有一个小程序,它是一个更大程序的一部分,我需要以与 itertools 相同的方式遍历从 1 到 10(可能或多或少)的数字数组的每个组合。但是,由于我有某些限制,我需要跳过大量这些组合以节省时间,因为这可能会变得非常大。

这是我的程序

combination = [-1, -1, -1, -1]
len_combination = len(combination)

max_at_index = [0, 2, 2, 1, 2, 1, 2, 1, 3, 1]
len_index = len(max_at_index)

end = 0


def skip(depth):

    combination[depth] = combination[depth] + 1
    if combination[depth] == len_index:
        combination[depth] = 0

    for x in range(0, len_index):
        if combination[:depth + 1].count(x) > max_at_index[x]:

            return True

    return False


for i in range(0, len_index):

    if skip(0):
        continue

    for j in range(0, len_index):

        if skip(1):
            continue

        for k in range(0, len_index):

            if skip(2):
                continue

            for l in range(0, len_index):

                if skip(3):
                    continue

                print(combination)

这个例子有 4 个项目,每个项目从 0 到 9 循环,([0, 0, 0, 0] 到 [9, 9, 9, 9])。但是,我的变量 max_at_index 限制了数组中每个索引处允许的值的计数。这里我们允许 0 0、2 1、2 2、1 3 等。这很好用,我什至可以扩展或缩小 max_at_index 数组。

我不知道该怎么做是使嵌套的 for 循环递归,这样我就可以扩展或缩小组合的大小以包含更多或更少的元素。

提前谢谢你。

编辑: 根据要求,对我的逻辑进行一些解释

考虑以下成本清单

[
[1, 2, 3, 4, 5, 6, 0, 8, 9],
[10, 11, 12, 0, 14, 15, 16, 17, 18, 19],
[0, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 0, 32, 33, 34, 35, 0, 37, 38, 0]
]

从每个数组中选择一个数字时,我必须生成尽可能小的总数...

  • 每个数组的数字不能为0
  • 每个所选数字的索引不能超过给定的限制(即索引 2 中不超过 3)
  • 从索引 0 中选择的数字数量必须达到限制(对于 示例 2 必须来自索引 0) 或下一个可能的最高值。

这部分我也想通了。如果我循环从 0,0,0,0 到 9,9,9,9 的每一个可能的组合,我可以测试它是否符合上述条件。我只需要避免循环每个组合,因为它们中的大多数都是无用的,而且会变大

【问题讨论】:

  • 你在循环中哪里使用 i,j,k,l ?
  • 订单对您来说重要吗?也就是说,您是否需要生成例如(0, 2, 1, 2)(2, 2, 1, 0) 作为不同的组合,或者不应该生成具有相同元素的元组?
  • 当我第一次写这个的时候,skip 函数是每个循环的一部分,而不是一个函数,它们在那里被使用并且现在是多余的
  • 重复很重要,每个组合都指向另一个数组中的索引。我有一个由 4 个数组组成的数组,每个数组有 10 个元素。这会从这些数组中构建每个值的组合(4 个中的 1 个)
  • 我还是建议结合正确的参数使用 itertools(可能多次调用它的函数)。这样,您不仅可以设置偏移量,还可以设置限制。您还可以使用 itertools 生成组合,然后使用您的自定义约束过滤它的输出。

标签: python loops for-loop recursion


【解决方案1】:

我认为这是一种可能的实现方式:

def bounded_comb(max_at_index, n):
    yield from _bounded_comb_rec(max_at_index, n, [0] * len(max_at_index), [])

def _bounded_comb_rec(max_at_index, n, counts, current):
    # If we have enough elements finish
    if len(current) >= n:
        yield tuple(current)
    else:
        # For each index and max
        for idx, m in enumerate(max_at_index):
            # If the max has not been reached
            if m > counts[idx]:
                # Add the index
                counts[idx] += 1
                current.append(idx)
                # Produce all combinations
                yield from _bounded_comb_rec(max_at_index, n, counts, current)
                # Undo add the index
                current.pop()
                counts[idx] -= 1

# Test
max_at_index = [0, 2, 1, 3]
n = 4
print(*bounded_comb(max_at_index, n), sep='\n')

输出:

(1, 1, 2, 3)
(1, 1, 3, 2)
(1, 1, 3, 3)
(1, 2, 1, 3)
(1, 2, 3, 1)
(1, 2, 3, 3)
(1, 3, 1, 2)
(1, 3, 1, 3)
(1, 3, 2, 1)
(1, 3, 2, 3)
(1, 3, 3, 1)
(1, 3, 3, 2)
(1, 3, 3, 3)
(2, 1, 1, 3)
(2, 1, 3, 1)
(2, 1, 3, 3)
(2, 3, 1, 1)
(2, 3, 1, 3)
(2, 3, 3, 1)
(2, 3, 3, 3)
(3, 1, 1, 2)
(3, 1, 1, 3)
(3, 1, 2, 1)
(3, 1, 2, 3)
(3, 1, 3, 1)
(3, 1, 3, 2)
(3, 1, 3, 3)
(3, 2, 1, 1)
(3, 2, 1, 3)
(3, 2, 3, 1)
(3, 2, 3, 3)
(3, 3, 1, 1)
(3, 3, 1, 2)
(3, 3, 1, 3)
(3, 3, 2, 1)
(3, 3, 2, 3)
(3, 3, 3, 1)
(3, 3, 3, 2)

【讨论】:

  • 感谢您的帮助。但是,如果此输出与我的输出不同,我正在查看从 [0,0,0,0] 到 [n,n,n,n] 的所有内容,其中 n 是 max_at_index 的长度(我的示例 [9,9, 9,9])但跳过每个数字的数量在 max_at_index 中受到限制的所有分支。例如将 0 限制为 2,第一个逻辑起始位置是 [0,0,1,1]。如果你运行我的程序,它将显示我想要的输出
  • @xn1 如果我在我的代码中设置max_at_index = [0, 2, 2, 1, 2, 1, 2, 1, 3, 1],我会得到与你的代码完全相同的输出(我更改了输入只是为了能够打印输出)。
  • 测试后我将其标记为答案,我根据 Born Tbe Wasted 的答案测试了你的答案,当组合的长度超过 6 个元素时,我发现你的答案最有可能发生。感谢您的帮助
  • 很抱歉问你这么多@jdehesa,但由于我有 32 个内核要处理,所以我想知道这部分的多处理。我熟悉多处理,所以这不是问题,我想知道是否可以将循环拆分为更小的循环,例如第一个处理器处理循环 0,0,0,0 到 0,9,9,9。接下来1,0,0,0到1,9,9,9都在相同的条件下吗?想法是每个都返回它的最佳结果,然后从中找到最佳结果。
  • @xn1 如果你想为每个组合计算一个值,然后选择最大值,你可以使用迭代器bounded_combprocess pool(例如方法map)来计算值并取max。如果您想将组合分组并为每个组合生成一个值,您可以执行相同的操作,但使用来自iterools recipesgrouper 之类的东西。还是您想并行生成组合?
【解决方案2】:

这是一个尝试,我限制构造一个可供选择的值池 (select_from),然后构建 combinations

from itertools import chain, combinations

max_at_index = [0, 2, 2, 1, 2, 1, 2, 1, 3, 1]

select_from = list(chain.from_iterable(n * [i] for i, n in enumerate(max_at_index)))
# [1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 8, 9]

for comb in combinations(select_from, 4):
    print(comb)

这会产生排序的组合。如果您还需要所有排列,则需要在之后执行此操作(我在此处使用 set 'seen' 以避免重复):

from itertools import chain, combinations, permutations

seen_comb = set()

select_from = list(chain.from_iterable(n * [i] for i, n in enumerate(max_at_index)))

for comb in combinations(select_from, 4):

    sorted_comb = tuple(sorted(comb))
    if sorted_comb in seen_comb:
        continue
    seen_comb.add(sorted_comb)

    seen_perm = set()

    for perm in permutations(comb):
        if perm in seen_perm:
            continue
        seen_perm.add(perm)

        print(perm)

【讨论】:

  • 这个差不多了,好像没有9开头的任意组合
  • 这看起来很有希望,确实谢谢。我只是要测试几次并报告
  • 使用第二个,我得到很多重复的结果,例如组合(select_from,2)前5个输出包含2个相同的结果。那是我做错了吗?
  • 你是对的...seen 需要是“全局的”...已修复,
  • 更新了一点。现在这将迭代更少的排列。但接受的答案看起来确实更好......
【解决方案3】:

我不想展示任何花哨的东西,而是给你递归循环最简单的答案(因为那是你的问题)

combination = [-1, -1, -1, -1]
len_combination = len(combination)
max_depth = 3
max_at_index = [0, 2, 2, 1, 2, 1, 2, 1, 3, 1]
len_index = len(max_at_index)

end = 0

def skip(depth):

    combination[depth] = combination[depth] + 1
    if combination[depth] == len_index:
        combination[depth] = 0

    for x in range(0, len_index):
        if combination[:depth + 1].count(x) > max_at_index[x]:

            return True,combination # Needs to return the state of combination

    return False,combination # Needs to return the state of combination

def loop(depth,combination):
    if depth == max_depth:
        boolean, combination = skip(depth)
        if not(boolean):
            print (combination)
            return combination
    else:
        for i in range(0, len_index):
            boolean, combination = skip(depth)
            if not(boolean):
                loop(depth+1,combination)

loop(0,combination)

【讨论】:

  • 这似乎确实工作得非常非常快,我会测试一下并回复你谢谢。
  • 感谢您的帮助。虽然您的答案完美无缺,并且在某些情况下比标记的答案更快,但在 max_depth 为 7 或更大的情况下速度较慢,这是我的用例最有可能发生的情况。
【解决方案4】:

sympy 还提供您需要的一切:

from sympy.utilities.iterables import multiset_permutations


max_at_index = [0, 2, 2, 1, 2, 1, 2, 1, 3, 1]
m_set = {i: n for  i, n in enumerate(max_at_index) if n != 0}

for perm in multiset_permutations(m_set, 4):
    print(perm)

说明:

它基于的数据类型是multiset(即元素可能出现多次的集合,但顺序无关紧要)。在sympy中有这样一个数据结构的函数:sympy.utilities.iterables.multiset

from itertools import chain
from sympy.utilities.iterables import multiset

max_at_index = [0, 2, 2, 1, 2, 1, 2, 1, 3, 1]
m_set = multiset(chain.from_iterable(n * [i] for i, n in enumerate(max_at_index)))
# {1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 2, 7: 1, 8: 3, 9: 1}

实际上multiset 只是返回一个dict;因此这更简单:

m_set = {i: n for  i, n in enumerate(max_at_index) if n != 0}
# {1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 2, 7: 1, 8: 3, 9: 1}

幸运的是sympy 也有permutecombine 那些多重集合的方法而不产生任何重复:

from sympy.utilities.iterables import multiset_permutations

for perm in multiset_permutations(m_set, 4):
    print(perm)

为了帮助并行化,首先计算组合可能会有所帮助:

from sympy.utilities.iterables import multiset_combinations, multiset_permutations

for comb in multiset_combinations(m_set, 4):
    print()
    for perm in multiset_permutations(comb):
        print(perm)

产生(在每个新组合后添加一个空格)

[1, 1, 2, 2]
[1, 2, 1, 2]
[1, 2, 2, 1]
[2, 1, 1, 2]
[2, 1, 2, 1]
[2, 2, 1, 1]

[1, 1, 2, 3]
[1, 1, 3, 2]
[1, 2, 1, 3]
[1, 2, 3, 1]
[1, 3, 1, 2]
[1, 3, 2, 1]
[2, 1, 1, 3]
[2, 1, 3, 1]
[2, 3, 1, 1]
[3, 1, 1, 2]
[3, 1, 2, 1]
[3, 2, 1, 1]

...

[8, 8, 8, 9]
[8, 8, 9, 8]
[8, 9, 8, 8]
[9, 8, 8, 8]

【讨论】:

  • 这很有希望,您认为可以控制循环的起点和终点吗?我目前的位置是(使用公认的答案)多处理循环(服务器有 32 个核心)。我的测试条件是max_at_index = [2, 2, 2, 2, 2, 2, 2],组合长度为8,每个循环增加一个计数器1。这个版本需要6.42秒。对另一个答案使用多重处理(每个进程创建循环 [n, 0, 0, 0, 0, 0, 0, 0] 到 [n, 9, 9, 9, 9, 9, 9, 9])需要 1.09秒。
  • 您可以尝试并行处理排列。排列的数量范围从 2520 到 20160...您还可以在启动线程以获取排列之前将一些组合组合在一起。我不知道如何调整起点和终点....
  • 一些统计数据(可能有助于并行化):共有 357 个组合(每个组合有 2520 到 20160 个排列),总共 2346120。
  • 我的最终基准是组合长度为 10,max_index 长度为 7。如果每个 max_index 设置为 7,那么将有 282,475,249 个组合,幸运的是,这永远不会出现这种情况,每个 2 都是更现实的案例。我不知道有多少组合。目前使用此文件github.com/Xn1ch1/Costing-Calculator/blob/master/… 我最多使用 17% 的 CPU。所以我渴望将循环拆分到更多处理器上
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-29
  • 2014-09-14
  • 1970-01-01
  • 1970-01-01
  • 2017-06-04
  • 2021-05-19
相关资源
最近更新 更多