More efficient way to get integer permutations?

2020-08-10 08:19发布

问题:

I can get integer permutations like this:

myInt = 123456789

l = itertools.permutations(str(myInt))
[int(''.join(x)) for x in l]

Is there a more efficient way to get integer permutations in Python, skipping the overhead of creating a string, then joining the generated tuples? Timing it, the tuple-joining process makes this 3x longer than list(l).

added supporting information

myInt =123456789
def v1(i): #timeit gives 258ms
    l = itertools.permutations(str(i))
    return [int(''.join(x)) for x in l]

def v2(i): #timeit gives 48ms
    l = itertools.permutations(str(i))
    return list(l)

def v3(i): #timeit gives 106 ms
    l = itertools.permutations(str(i))
    return [''.join(x) for x in l]

回答1:

You can do:

>>> digits = [int(x) for x in str(123)]
>>> n_digits = len(digits)
>>> n_power = n_digits - 1
>>> permutations = itertools.permutations(digits)
>>> [sum(v * (10**(n_power - i)) for i, v in enumerate(item)) for item in permutations]
[123, 132, 213, 231, 312, 321]

This avoids conversion to and from a tuple as it'll use the integer's position in the tuple to compute its value (e.g., (1,2,3) means 100 + 20 + 3).

Because the value of n_digits is known and the same throughout the process, I think you can also optimize the computations to:

>>> values = [v * (10**(n_power - i)) for i, v in enumerate(itertools.repeat(1, n_digits))]
>>> values
[100, 10, 1]
>>> [sum(v * index for v, index in zip(item, values)) for item in permutations]
[123, 132, 213, 231, 312, 321]

I also think we don't need to call zip() all the time because we don't need that list:

>>> positions = list(xrange(n_digits))
>>> [sum(item[x] * values[x] for x in positions) for item in permutations]
[123, 132, 213, 231, 312, 321]


回答2:

This will give you a generator:

import itertools as it
gen = it.permutations(range(1, 10))

Then you can iterate over each item:

for i in gen:
    #some code

Or convert it to a list, but it'll take some time:

items = list(gen)

EDIT: Clarified that you want back an integer, maybe the fastest way is using another lazy evaluation:

gen = (int('%d%d%d%d%d%d%d%d%d' % x) for x in it.permutations(range(1, 10)))


回答3:

I can't comment on Simeon's answer, so I'm adding this here.

If you try to permute 120 with the function in the answer, you get

[120,102,210,201,12,21]

12 and 21 are wrong answers, so I made a modification to discard them:

def permute(n):
        digits = [int(x) for x in str(n)]
        n_digits = len(digits)
        n_power = n_digits - 1
        values = [v * (10**(n_power - i)) for i, v in
            enumerate(itertools.repeat(1, n_digits))]
        positions = list(range(n_digits))
        permutations = {sum(item[x] * values[x] for x in positions) for
            item in itertools.permutations(digits) if item[0] > 0}
        for p in permutations:
            yield p

Edit: also forgot to add that the function will count the same digits twice, leaving you with duplicates, so I modified that as well.