问题
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