Comment générer toutes les permutations d'une liste en Python, indépendamment du type des éléments de cette liste?
Par exemple:
permutations([])
[]
permutations([1])
[1]
permutations([1, 2])
[1, 2]
[2, 1]
permutations([1, 2, 3])
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
_ {À partir de Python 2.6} _ (et si vous êtes sur Python 3), vous disposez d'un outil bibliothèque standard pour cela: itertools.permutations
.
import itertools
list(itertools.permutations([1, 2, 3]))
Si vous utilisez un ancien Python (<2.6) pour une raison quelconque ou êtes simplement curieux de savoir comment cela fonctionne, voici une approche intéressante, tirée de http://code.activestate.com/ recettes/252178/ :
def all_perms(elements):
if len(elements) <=1:
yield elements
else:
for perm in all_perms(elements[1:]):
for i in range(len(elements)):
# nb elements[0:1] works in both string and list contexts
yield perm[:i] + elements[0:1] + perm[i:]
Quelques approches alternatives sont répertoriées dans la documentation de itertools.permutations
. En voici un:
def permutations(iterable, r=None):
# permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
# permutations(range(3)) --> 012 021 102 120 201 210
pool = Tuple(iterable)
n = len(pool)
r = n if r is None else r
if r > n:
return
indices = range(n)
cycles = range(n, n-r, -1)
yield Tuple(pool[i] for i in indices[:r])
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield Tuple(pool[i] for i in indices[:r])
break
else:
return
Et un autre, basé sur itertools.product
:
def permutations(iterable, r=None):
pool = Tuple(iterable)
n = len(pool)
r = n if r is None else r
for indices in product(range(n), repeat=r):
if len(set(indices)) == r:
yield Tuple(pool[i] for i in indices)
Et dans Python 2.6 à partir de:
import itertools
itertools.permutations([1,2,3])
(retourné en tant que générateur. Utilisez list(permutations(l))
pour revenir en tant que liste.)
Le code suivant avec Python 2.6 et supérieur UNIQUEMENT
Commencez par importer itertools
:
import itertools
print list(itertools.permutations([1,2,3,4], 2))
[(1, 2), (1, 3), (1, 4),
(2, 1), (2, 3), (2, 4),
(3, 1), (3, 2), (3, 4),
(4, 1), (4, 2), (4, 3)]
print list(itertools.combinations('123', 2))
[('1', '2'), ('1', '3'), ('2', '3')]
print list(itertools.product([1,2,3], [4,5,6]))
[(1, 4), (1, 5), (1, 6),
(2, 4), (2, 5), (2, 6),
(3, 4), (3, 5), (3, 6)]
print list(itertools.product([1,2], repeat=3))
[(1, 1, 1), (1, 1, 2), (1, 2, 1), (1, 2, 2),
(2, 1, 1), (2, 1, 2), (2, 2, 1), (2, 2, 2)]
def permutations(head, tail=''):
if len(head) == 0: print tail
else:
for i in range(len(head)):
permutations(head[0:i] + head[i+1:], tail+head[i])
appelé comme:
permutations('abc')
Cette solution implémente un générateur, pour éviter de conserver toutes les permutations en mémoire:
def permutations (orig_list):
if not isinstance(orig_list, list):
orig_list = list(orig_list)
yield orig_list
if len(orig_list) == 1:
return
for n in sorted(orig_list):
new_list = orig_list[:]
pos = new_list.index(n)
del(new_list[pos])
new_list.insert(0, n)
for resto in permutations(new_list[1:]):
if new_list[:1] + resto <> orig_list:
yield new_list[:1] + resto
#!/usr/bin/env python
def perm(a, k=0):
if k == len(a):
print a
else:
for i in xrange(k, len(a)):
a[k], a[i] = a[i] ,a[k]
perm(a, k+1)
a[k], a[i] = a[i], a[k]
perm([1,2,3])
Sortie:
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 2, 1]
[3, 1, 2]
Comme j'échange le contenu de la liste, il faut un type de séquence mutable en entrée. Par exemple. perm(list("ball"))
fonctionnera et perm("ball")
ne fonctionnera pas car vous ne pouvez pas modifier une chaîne.
Cette implémentation Python est inspirée de l’algorithme présenté dans le livre Computer Algorithms de Horowitz, Sahni et Rajasekeran .
Le code suivant est une permutation sur place d'une liste donnée, implémentée en tant que générateur. Etant donné qu'elle ne renvoie que des références à la liste, celle-ci ne doit pas être modifiée en dehors du générateur ..__ La solution est non récursive et utilise donc une mémoire faible. Fonctionne bien aussi avec plusieurs copies d’éléments de la liste de saisie.
def permute_in_place(a):
a.sort()
yield list(a)
if len(a) <= 1:
return
first = 0
last = len(a)
while 1:
i = last - 1
while 1:
i = i - 1
if a[i] < a[i+1]:
j = last - 1
while not (a[i] < a[j]):
j = j - 1
a[i], a[j] = a[j], a[i] # swap the values
r = a[i+1:last]
r.reverse()
a[i+1:last] = r
yield list(a)
break
if i == first:
a.reverse()
return
if __== '__main__':
for n in range(5):
for a in permute_in_place(range(1, n+1)):
print a
print
for a in permute_in_place([0, 0, 1, 1, 1]):
print a
print
Une manière assez évidente à mon avis pourrait aussi être:
def permutList(l):
if not l:
return [[]]
res = []
for e in l:
temp = l[:]
temp.remove(e)
res.extend([[e] + r for r in permutList(temp)])
return res
Dans un style fonctionnel
def addperm(x,l):
return [ l[0:i] + [x] + l[i:] for i in range(len(l)+1) ]
def perm(l):
if len(l) == 0:
return [[]]
return [x for y in perm(l[1:]) for x in addperm(l[0],y) ]
print perm([ i for i in range(3)])
Le résultat:
[[0, 1, 2], [1, 0, 2], [1, 2, 0], [0, 2, 1], [2, 0, 1], [2, 1, 0]]
list2Perm = [1, 2.0, 'three']
listPerm = [[a, b, c]
for a in list2Perm
for b in list2Perm
for c in list2Perm
if ( a != b and b != c and a != c )
]
print listPerm
Sortie:
[
[1, 2.0, 'three'],
[1, 'three', 2.0],
[2.0, 1, 'three'],
[2.0, 'three', 1],
['three', 1, 2.0],
['three', 2.0, 1]
]
J'ai utilisé un algorithme basé sur le système de nombres factoriels - Pour une liste de longueur n, vous pouvez assembler chaque permutation, élément par élément, en choisissant parmi les éléments restants à chaque étape. Vous avez n choix pour le premier élément, n-1 pour le second et un seul pour le dernier, vous pouvez donc utiliser les chiffres d'un nombre dans le système de nombres factoriels comme index. Ainsi, les nombres de 0 à n! -1 correspondent à toutes les permutations possibles dans l'ordre lexicographique.
from math import factorial
def permutations(l):
permutations=[]
length=len(l)
for x in xrange(factorial(length)):
available=list(l)
newPermutation=[]
for radix in xrange(length, 0, -1):
placeValue=factorial(radix-1)
index=x/placeValue
newPermutation.append(available.pop(index))
x-=index*placeValue
permutations.append(newPermutation)
return permutations
permutations(range(3))
sortie:
[[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
Cette méthode est non-récursive, mais elle est légèrement plus lente sur mon ordinateur et xrange génère une erreur lorsque n! est trop grand pour être converti en un entier long (n = 13 pour moi). C’était suffisant quand j’en avais besoin, mais ce n’est pas un itertools.permutations à long terme.
Notez que cet algorithme a une complexité temporelle n factorial
, où n
est la longueur de la liste d'entrée
Imprimez les résultats sur le pouce:
global result
result = []
def permutation(li):
if li == [] or li == None:
return
if len(li) == 1:
result.append(li[0])
print result
result.pop()
return
for i in range(0,len(li)):
result.append(li[i])
permutation(li[:i] + li[i+1:])
result.pop()
Exemple:
permutation([1,2,3])
Sortie:
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
On peut en effet parcourir le premier élément de chaque permutation, comme dans la réponse de tzwenn; Je préfère écrire cette solution de cette façon:
def all_perms(elements):
if len(elements) <= 1:
yield elements # Only permutation possible = no permutation
else:
# Iteration over the first element in the result permutation:
for (index, first_elmt) in enumerate(elements):
other_elmts = elements[:index]+elements[index+1:]
for permutation in all_perms(other_elmts):
yield [first_elmt] + permutation
Cette solution est environ 30% plus rapide, apparemment grâce à la récursivité se terminant par len(elements) <= 1
au lieu de 0
. Elle est également beaucoup plus efficace en termes de mémoire, car elle utilise une fonction de générateur (via yield
), comme dans la solution de Riccardo Reyes.
Ceci est inspiré de l'implémentation Haskell utilisant la compréhension de liste:
def permutation(list):
if len(list) == 0:
return [[]]
else:
return [[x] + ys for x in list for ys in permutation(delete(list, x))]
def delete(list, item):
lc = list[:]
lc.remove(item)
return lc
Pour la performance, une solution numpy inspirée de Knuth , (p22):
from numpy import empty, uint8
from math import factorial
def perms(n):
f = 1
p = empty((2*n-1, factorial(n)), uint8)
for i in range(n):
p[i, :f] = i
p[i+1:2*i+1, :f] = p[:i, :f] # constitution de blocs
for j in range(i):
p[:i+1, f*(j+1):f*(j+2)] = p[j+1:j+i+2, :f] # copie de blocs
f = f*(i+1)
return p[:n, :]
Copier de grands blocs de mémoire fait gagner du temps - 20 fois plus rapide que list(itertools.permutations(range(n))
:
In [1]: %timeit -n10 list(permutations(range(10)))
10 loops, best of 3: 815 ms per loop
In [2]: %timeit -n100 perms(10)
100 loops, best of 3: 40 ms per loop
Pardonnez mon analphabétisme python car je ne proposerai pas la solution en python . Comme je ne sais pas quelle méthode python 2.6 utilise pour générer les permutations et que celui d’éliben ressemble à la génération de permutation de Johnson-Trotter, vous pouvez rechercher un articlein Wikipedia sur Les permutations et leur génération qui ressemble assez à une fonction non encombrée dans papier de Myrvold et Ruskey .
Il me semble que cela pourrait être utilisé dans un générateur de la même manière que dans d'autres réponses pour réduire considérablement les besoins en mémoire. Rappelez-vous simplement que les permutations ne seront pas dans l'ordre lexicographique.
from __future__ import print_function
def perm(n):
p = []
for i in range(0,n+1):
p.append(i)
while True:
for i in range(1,n+1):
print(p[i], end=' ')
print("")
i = n - 1
found = 0
while (not found and i>0):
if p[i]<p[i+1]:
found = 1
else:
i = i - 1
k = n
while p[i]>p[k]:
k = k - 1
aux = p[i]
p[i] = p[k]
p[k] = aux
for j in range(1,(n-i)/2+1):
aux = p[i+j]
p[i+j] = p[n-j+1]
p[n-j+1] = aux
if not found:
break
perm(5)
def pzip(c, seq):
result = []
for item in seq:
for i in range(len(item)+1):
result.append(item[i:]+c+item[:i])
return result
def perm(line):
seq = [c for c in line]
if len(seq) <=1 :
return seq
else:
return pzip(seq[0], perm(seq[1:]))
Voici un algorithme qui fonctionne sur une liste sans créer de nouvelles listes intermédiaires similaires à la solution de Ber sur https://stackoverflow.com/a/108651/184528 .
def permute(xs, low=0):
if low + 1 >= len(xs):
yield xs
else:
for p in permute(xs, low + 1):
yield p
for i in range(low + 1, len(xs)):
xs[low], xs[i] = xs[i], xs[low]
for p in permute(xs, low + 1):
yield p
xs[low], xs[i] = xs[i], xs[low]
for p in permute([1, 2, 3, 4]):
print p
Vous pouvez essayer le code par vous-même ici: http://repl.it/J9v
La beauté de la récursion:
>>> import copy
>>> def perm(prefix,rest):
... for e in rest:
... new_rest=copy.copy(rest)
... new_prefix=copy.copy(prefix)
... new_prefix.append(e)
... new_rest.remove(e)
... if len(new_rest) == 0:
... print new_prefix + new_rest
... continue
... perm(new_prefix,new_rest)
...
>>> perm([],['a','b','c','d'])
['a', 'b', 'c', 'd']
['a', 'b', 'd', 'c']
['a', 'c', 'b', 'd']
['a', 'c', 'd', 'b']
['a', 'd', 'b', 'c']
['a', 'd', 'c', 'b']
['b', 'a', 'c', 'd']
['b', 'a', 'd', 'c']
['b', 'c', 'a', 'd']
['b', 'c', 'd', 'a']
['b', 'd', 'a', 'c']
['b', 'd', 'c', 'a']
['c', 'a', 'b', 'd']
['c', 'a', 'd', 'b']
['c', 'b', 'a', 'd']
['c', 'b', 'd', 'a']
['c', 'd', 'a', 'b']
['c', 'd', 'b', 'a']
['d', 'a', 'b', 'c']
['d', 'a', 'c', 'b']
['d', 'b', 'a', 'c']
['d', 'b', 'c', 'a']
['d', 'c', 'a', 'b']
['d', 'c', 'b', 'a']
Cet algorithme est le plus efficace, il évite le passage de tableaux et la manipulation dans les appels récursifs, fonctionne en Python 2, 3
def permute(items):
length = len(items)
def inner(ix=[]):
do_yield = len(ix) == length - 1
for i in range(0, length):
if i in ix: #avoid duplicates
continue
if do_yield:
yield Tuple([items[y] for y in ix + [i]])
else:
for p in inner(ix + [i]):
yield p
return inner()
Usage:
for p in permute((1,2,3)):
print(p)
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
Générer toutes les permutations possibles
J'utilise python3.4:
def calcperm(arr, size):
result = set([()])
for dummy_idx in range(size):
temp = set()
for dummy_lst in result:
for dummy_outcome in arr:
if dummy_outcome not in dummy_lst:
new_seq = list(dummy_lst)
new_seq.append(dummy_outcome)
temp.add(Tuple(new_seq))
result = temp
return result
Cas de test:
lst = [1, 2, 3, 4]
#lst = ["yellow", "Magenta", "white", "blue"]
seq = 2
final = calcperm(lst, seq)
print(len(final))
print(final)
UNE AUTRE APPROCHE (sans bibliothèque)
def permutation(input):
if len(input) == 1:
return input if isinstance(input, list) else [input]
result = []
for i in range(len(input)):
first = input[i]
rest = input[:i] + input[i + 1:]
rest_permutation = permutation(rest)
for p in rest_permutation:
result.append(first + p)
return result
L'entrée peut être une chaîne ou une liste
print(permutation('abcd'))
print(permutation(['a', 'b', 'c', 'd']))
Je vois un beaucoup d'itération qui se passe dans ces fonctions récursives, pas exactement pur récursivité ...
donc, pour ceux qui ne peuvent pas même suivre une seule boucle, voici une solution totalement récursive, totalement inutile et totalement inutile.
def all_insert(x, e, i=0):
return [x[0:i]+[e]+x[i:]] + all_insert(x,e,i+1) if i<len(x)+1 else []
def for_each(X, e):
return all_insert(X[0], e) + for_each(X[1:],e) if X else []
def permute(x):
return [x] if len(x) < 2 else for_each( permute(x[1:]) , x[0])
perms = permute([1,2,3])
Une autre solution:
def permutation(flag, k =1 ):
N = len(flag)
for i in xrange(0, N):
if flag[i] != 0:
continue
flag[i] = k
if k == N:
print flag
permutation(flag, k+1)
flag[i] = 0
permutation([0, 0, 0])
Pour vous faire économiser des heures possibles de recherche et d'expérimentation, voici la solution de permutaions non récursive en Python qui fonctionne également avec Numba (à partir de v. 0.41):
@numba.njit()
def permutations(A, k):
r = [[i for i in range(0)]]
for i in range(k):
r = [[a] + b for a in A for b in r if (a in b)==False]
return r
permutations([1,2,3],3)
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
Pour donner une impression de performance:
%timeit permutations(np.arange(5),5)
243 µs ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
time: 406 ms
%timeit list(itertools.permutations(np.arange(5),5))
15.9 µs ± 8.61 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
time: 12.9 s
Utilisez donc cette version uniquement si vous devez l’appeler à partir de la fonction njitted, sinon préférez l’implémentation d’itertools.
def permutation(Word, first_char=None):
if Word == None or len(Word) == 0: return []
if len(Word) == 1: return [Word]
result = []
first_char = Word[0]
for sub_Word in permutation(Word[1:], first_char):
result += insert(first_char, sub_Word)
return sorted(result)
def insert(ch, sub_Word):
arr = [ch + sub_Word]
for i in range(len(sub_Word)):
arr.append(sub_Word[i:] + ch + sub_Word[:i])
return arr
assert permutation(None) == []
assert permutation('') == []
assert permutation('1') == ['1']
assert permutation('12') == ['12', '21']
print permutation('abc')
Sortie: ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
Utiliser Counter
from collections import Counter
def permutations(nums):
ans = [[]]
cache = Counter(nums)
for idx, x in enumerate(nums):
result = []
for items in ans:
cache1 = Counter(items)
for id, n in enumerate(nums):
if cache[n] != cache1[n] and items + [n] not in result:
result.append(items + [n])
ans = result
return ans
permutations([1, 2, 2])
> [[1, 2, 2], [2, 1, 2], [2, 2, 1]]
Ma solution Python:
def permutes(input,offset):
if( len(input) == offset ):
return [''.join(input)]
result=[]
for i in range( offset, len(input) ):
input[offset], input[i] = input[i], input[offset]
result = result + permutes(input,offset+1)
input[offset], input[i] = input[i], input[offset]
return result
# input is a "string"
# return value is a list of strings
def permutations(input):
return permutes( list(input), 0 )
# Main Program
print( permutations("wxyz") )