Adding constraints to permutations

℡╲_俬逩灬. 提交于 2020-01-02 15:47:12

问题


I am trying to calculate all permutations with list = [0,1,2,3,4,5,6] adhering to some constraints.

  • Position 0 must be > 3
  • Position 3 + Position 5 < Position 4

With my current code I determining all permutations and iterations through each, applying the constraints.

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 ###
        ####################################################

The problem with this code is that I'm wasting time finding all possible permutations just to filter them out again. Can someone assist with a way to apply the constraints while permutations are determined so not all N! are determined?


回答1:


Instead of first creating a list of all the permutations and then adding some of those elements to a second list and discarding the rest (about 90% in this case), you can use a list comprehension to filter the permutations yielded by itertools as they are created.

>>> 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 the checks become more complicated, you do not even have to use a list comprehension. You can do the same in a regular for loop with if conditions, the only difference being that you do not collect all the permutations in a list first, but iterate the generator directly:

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

Both approaches will still generate all the N! permutations, but most are immediately discarded and only the "right" ones are ever stored in the list.


If you do not even want to generate them, you will have to implement a custom recursive permutations algorithm and manually check whether e.g. for a given value for p[3] there can be any valid values for p[5] and so on. Something like this:

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

This checks the two constraints while generating the permutations, so that if e.g. all the permutations starting with a number <= 3 are not generated at all. The second check is a bit more complicated, and could probably be improved further (if we add a counter to the beginning of the function, we see that there are ~1200 recursive calls). Anyway, using IPython's %timeit we see that the "manual" approach with checks on the fly is still about three times slower than using itertools, so even improving the checks will probably not make it faster than that.*) Also, your own original loop is, in fact, not that much slower, either.

>>> %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

Of course, depending on the size of the input list and the constraints, the manual approach can present more or less of a saving, but can also be (much) more or less difficult to implement or to adapt to changing constraints. Personally, I'd still go with using itertools.permutations and a few simple, readable filters.


*) Update: In my previous edit, the manual approach came out faster; this was because I forgot to actually consume the generators returned by the two functions.



来源:https://stackoverflow.com/questions/48442677/adding-constraints-to-permutations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!