有没有办法将此嵌套循环更改为递归循环?

时间:2019-04-01 09:48:36

标签: python loops for-loop recursion

我正在寻找有关以下问题的帮助。我有一个小程序,它是更大程序的一部分,我需要以与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 0s,2 1s,2 2s,1 3s等。这很好,我什至可以扩展或缩小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循环,我可以测试一下是否满足上述条件。我只需要避免循环每个组合,因为它们中的大多数将无用,而且会变得很大

4 个答案:

答案 0 :(得分:3)

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

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)

答案 1 :(得分:1)

这是尝试,我限制构造一个值池以从(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)

答案 2 :(得分:1)

我不想表现出任何幻想,但想为您提供递归循环的最简单答案(因为那是您的问题)

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)

答案 3 :(得分:1)

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]