【问题标题】:Adding constraints to permutations向排列添加约束
【发布时间】:2018-01-25 12:16:56
【问题描述】:

我正在尝试使用 list = [0,1,2,3,4,5,6] 计算所有排列,并遵守一些约束。

  • 位置 0 必须 > 3
  • 位置 3 + 位置 5

使用我当前的代码,我确定所有排列和迭代,并应用约束。

import itertools

Used_permutations = []    
numbers = [0,1,2,3,4,5,6]
all_permutations = list(itertools.permutations(numbers)) #Determine all permutations

for permutation in all_permutations:
    if permutation[0] > 3 and permutation[3] + permutation[5] < permutation[4]: #Constraints applied to permutations
        Used_permutations.append(permutation)  
        ####################################################
        ###   Apply calculations to current permutation ###
        ####################################################

这段代码的问题是我浪费时间寻找所有可能的排列,只是为了再次过滤掉它们。有人可以协助在确定排列时应用约束的方法,因此并非全部为 N!确定了吗?

【问题讨论】:

  • 位置 0 为 > 3 表示位置 0 4,因为 4 是唯一满足该条件的候选者。
  • 假设位置 3、4 和 5 是指 2、3 和 4,那么满足您的条件的 只有四个排列。 [4, 2, 0, 3, 1][4, 2, 1, 3, 0][4, 3, 0, 2, 1][4, 3, 1, 2, 0]
  • 忘记了这两个[4, 1, 0, 3, 2][4, 1, 2, 3, 0]。所以总共6个
  • 抱歉,复制了错误的列表。编辑以更正列表。
  • @Pierre 现在太晚了,伙计..

标签: python permutation


【解决方案1】:

您可以使用列表推导过滤产生的排列,而不是首先创建所有排列的list,然后将其中一些元素添加到第二个列表并丢弃其余元素(在这种情况下约为 90%)由itertools 创建。

>>> numbers = [0,1,2,3,4,5,6]
>>> [p for p in itertools.permutations(numbers) if p[0] > 3 and p[3] + p[5] < p[4]]
[(4, 0, 1, 2, 6, 3, 5),
 (4, 0, 1, 3, 6, 2, 5),
 ... a few more ...
 (6, 5, 4, 1, 3, 0, 2),
 (6, 5, 4, 2, 3, 0, 1)]
>>> len(_)
516

如果检查变得更复杂,您甚至不必使用列表推导式。您可以在带有if 条件的常规for 循环中执行相同的操作,唯一的区别是您不首先收集list 中的所有排列,而是直接迭代生成器:

all_permutations = itertools.permutations(numbers)  # no list(...)
for permutation in all_permutations:
    ...

这两种方法仍将生成所有 N!排列,但大多数会立即被丢弃,只有“正确”的排列存储在列表中。


如果您甚至不想生成它们,则必须实现自定义递归 permutations 算法并手动检查是否例如对于p[3] 的给定值,p[5] 可以有任何有效值,依此类推。像这样的:

def manual_permutations(numbers, n=0, last=0, lastlast=0):
    if len(numbers) <= 1:
        yield tuple(numbers)
    else:
        for i, first in enumerate(numbers):
            # first constraint
            if n == 0 and first <= 3: continue
            # second constraint: 3 + 5 < 4 === 3 - 4 < -5 === 3 < 4 - 5
            # assuming numbers are ordered: rest[0] is min, rest[-1] is max
            rest = numbers[:i] + numbers[i+1:]
            if n == 3 and first >= rest[-1] - rest[0]: continue
            if n == 4 and last - first >= - rest[0]: continue
            if n == 5 and lastlast - last >= - first: continue
            # constraints okay, recurse
            for perm in manual_permutations(rest, n+1, first, last):
                yield (first,) + perm

这会在生成排列时检查两个约束,这样如果例如根本不会生成以数字&lt;= 3 开头的所有排列。第二个检查有点复杂,可能会进一步改进(如果我们在函数的开头添加一个计数器,我们会看到大约有 1200 个递归调用)。无论如何,使用 IPython 的 %timeit,我们看到动态检查的“手动”方法仍然比使用 itertools 慢三倍,因此即使改进检查也可能不会使其更快*) 此外,您自己的原始循环实际上也没有那么慢。

>>> %timeit original_loop(numbers)
1000 loops, best of 3: 736 µs per loop

>>> %timeit list(itertools_perms(numbers))
1000 loops, best of 3: 672 µs per loop

>>> %timeit list(manual_permutations(numbers))
100 loops, best of 3: 2.11 ms per loop

当然,根据输入列表的大小和约束,手动方法可以或多或少地节省成本,但也可能(或多或少)难以实施或适应不断变化的约束。就个人而言,我仍然会使用itertools.permutations 和一些简单易读的过滤器。


*) 更新:在我之前的编辑中,手动方法出来得更快;这是因为我忘了真正使用这两个函数返回的生成器。

【讨论】:

  • 您能否详细说明我将如何实现自定义递归置换算法。我一直在尝试将约束直接添加到递归函数中,但无济于事。我在link 中使用了与 Silveira Neto 的解决方案类似的递归函数。
  • @Pierre 请看我的新编辑。我在之前的时序分析中犯了一个错误,使用itertools实际上更快。
猜你喜欢
  • 2016-07-08
  • 2016-01-17
  • 1970-01-01
  • 1970-01-01
  • 2018-03-26
  • 1970-01-01
  • 2013-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多