itertools.permutations génère des points où ses éléments sont traités comme uniques en fonction de leur position et non de leur valeur. Donc, fondamentalement, je veux éviter les doublons comme celui-ci:
>>> list(itertools.permutations([1, 1, 1]))
[(1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1)]
Filtrer ensuite n'est pas possible car la quantité de permutations est trop grande dans mon cas.
Est-ce que quelqu'un connaît un algorithme approprié pour cela?
Merci beaucoup!
MODIFIER:
Ce que je veux fondamentalement, c'est ce qui suit:
x = itertools.product((0, 1, 'x'), repeat=X)
x = sorted(x, key=functools.partial(count_elements, elem='x'))
ce qui n'est pas possible car sorted
crée une liste et que la sortie de itertools.product est trop grande.
Désolé, j'aurais dû décrire le problème actuel.
class unique_element:
def __init__(self,value,occurrences):
self.value = value
self.occurrences = occurrences
def perm_unique(elements):
eset=set(elements)
listunique = [unique_element(i,elements.count(i)) for i in eset]
u=len(elements)
return perm_unique_helper(listunique,[0]*u,u-1)
def perm_unique_helper(listunique,result_list,d):
if d < 0:
yield Tuple(result_list)
else:
for i in listunique:
if i.occurrences > 0:
result_list[d]=i.value
i.occurrences-=1
for g in perm_unique_helper(listunique,result_list,d-1):
yield g
i.occurrences+=1
a = list(perm_unique([1,1,2]))
print(a)
résultat:
[(2, 1, 1), (1, 2, 1), (1, 1, 2)]
EDIT (comment ça marche):
J'ai réécrit le programme supérieur pour qu'il soit plus long mais plus lisible
J'ai généralement du mal à expliquer comment quelque chose fonctionne, mais laissez-moi essayer… .. Pour comprendre comment cela fonctionne, vous devez comprendre un programme similaire, mais plus simple qui donnerait toutes les permutations avec répétition.
def permutations_with_replacement(elements,n):
return permutations_helper(elements,[0]*n,n-1)#this is generator
def permutations_helper(elements,result_list,d):
if d<0:
yield Tuple(result_list)
else:
for i in elements:
result_list[d]=i
all_permutations = permutations_helper(elements,result_list,d-1)#this is generator
for g in all_permutations:
yield g
Ce programme est évidemment beaucoup plus simple: D représente la profondeur dans permutations_helper et remplit deux fonctions. Une fonction est la condition d’arrêt de notre algorithme récursif et une autre pour la liste de résultats, qui est transmise.
Au lieu de renvoyer chaque résultat, nous le cédons. S'il n'y avait pas de fonction/opérateur yield
, nous devions pousser le résultat dans une file d'attente au point d'arrêt. Mais de cette façon, une fois que la condition d’arrêt est satisfaite, le résultat est propagé dans l’ensemble jusqu’à l'appelant. C'est le but defor g in perm_unique_helper(listunique,result_list,d-1): yield g
so chaque résultat est propagé jusqu'à l'appelant.
Retour au programme original: .__ Nous avons la liste des éléments uniques. Avant de pouvoir utiliser chaque élément, nous devons vérifier combien d’entre eux sont encore disponibles pour le pousser dans result_list. Le fonctionnement de ce programme est très similaire à la différence avec permutations_with_replacement
: chaque élément ne peut pas être répété plusieurs fois dans perm_unique_helper.
Parce que parfois les nouvelles questions sont marquées comme des doublons et que leurs auteurs sont référés à cette question, il peut être important de mentionner que sympy a un itérateur à cet effet.
>>> from sympy.utilities.iterables import multiset_permutations
>>> list(multiset_permutations([1,1,1]))
[[1, 1, 1]]
>>> list(multiset_permutations([1,1,2]))
[[1, 1, 2], [1, 2, 1], [2, 1, 1]]
Cela repose sur le détail de la mise en œuvre selon lequel les permutations d'un itératif trié sont triées sauf si elles sont des duplications de permutations antérieures.
from itertools import permutations
def unique_permutations(iterable, r=None):
previous = Tuple()
for p in permutations(sorted(iterable), r):
if p > previous:
previous = p
yield p
for p in unique_permutations('cabcab', 2):
print p
donne
('a', 'a')
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'b')
('b', 'c')
('c', 'a')
('c', 'b')
('c', 'c')
Vous pouvez essayer d'utiliser set:
>>> list(itertools.permutations(set([1,1,2,2])))
[(1, 2), (2, 1)]
L'appel à définir les doublons supprimés
À peu près aussi vite que la réponse de Luka Rahne, mais plus simple et plus simple, à mon humble avis.
def unique_permutations(elements):
if len(elements) == 1:
yield (elements[0],)
else:
unique_elements = set(elements)
for first_element in unique_elements:
remaining_elements = list(elements)
remaining_elements.remove(first_element)
for sub_permutation in unique_permutations(remaining_elements):
yield (first_element,) + sub_permutation
>>> list(unique_permutations((1,2,3,1)))
[(1, 1, 2, 3), (1, 1, 3, 2), (1, 2, 1, 3), ... , (3, 1, 2, 1), (3, 2, 1, 1)]
Cela fonctionne de manière récursive en définissant le premier élément (en parcourant tous les éléments uniques) et en parcourant les permutations de tous les éléments restants.
Passons par le unique_permutations
de (1,2,3,1) pour voir comment cela fonctionne:
unique_elements
sont 1,2,3first_element
commence par 1 .remaining_elements
sont [2,3,1] (c'est-à-dire 1,2,3,1 moins le premier)sub_permutation
, nous insérons le first_element
: ( 1 , 1,2,3), ( 1 , 1,3,2), ... et donnons le résultat.first_element
= 2 et faisons comme ci-dessus .remaining_elements
sont [1,3,1] (c'est-à-dire 1,2,3,1 moins les 2 premiers)sub_permutation
, nous insérons le first_element
: ( 2 , 1, 1, 3), ( 2 , 1, 3, 1), ( 2 , 3, 1, 1) ... et donner le résultat.first_element
= 3.Ceci est ma solution avec 10 lignes:
class Solution(object):
def permute_unique(self, nums):
perms = [[]]
for n in nums:
new_perm = []
for perm in perms:
for i in range(len(perm) + 1):
new_perm.append(perm[:i] + [n] + perm[i:])
# handle duplication
if i < len(perm) and perm[i] == n: break
perms = new_perm
return perms
if __== '__main__':
s = Solution()
print s.permute_unique([1, 1, 1])
print s.permute_unique([1, 2, 1])
print s.permute_unique([1, 2, 3])
--- Résultat ----
[[1, 1, 1]]
[[1, 2, 1], [2, 1, 1], [1, 1, 2]]
[[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]]
Une approche naïve pourrait être de prendre l’ensemble des permutations:
list(set(it.permutations([1, 1, 1])))
# [(1, 1, 1)]
Cependant, cette technique calcule inutilement des permutations de réplication et les rejette. Une approche plus efficace consisterait en _ { more_itertools.distinct_permutations
, un outil tiers .
Code
import itertools as it
import more_itertools as mit
list(mit.distinct_permutations([1, 1, 1]))
# [(1, 1, 1)]
Performance
En utilisant un plus grand nombre itérable, nous allons comparer les performances entre les techniques naïves et tierces.
iterable = [1, 1, 1, 1, 1, 1]
len(list(it.permutations(iterable)))
# 720
%timeit -n 10000 list(set(it.permutations(iterable)))
# 10000 loops, best of 3: 111 µs per loop
%timeit -n 10000 list(mit.distinct_permutations(iterable))
# 10000 loops, best of 3: 16.7 µs per loop
Nous voyons que more_itertools.distinct_permutations
est un ordre de grandeur plus rapide.
Détails
À partir de la source, un algorithme de récursivité (comme indiqué dans la réponse acceptée) est utilisé pour calculer des permutations distinctes, évitant ainsi les calculs inutiles. Voir le code source pour plus de détails.
On dirait que vous cherchez itertools.combinations () docs.python.org
list(itertools.combinations([1, 1, 1],3))
[(1, 1, 1)]
Je me suis heurté à cette question en cherchant quelque chose moi-même!
Voici ce que j'ai fait:
def dont_repeat(x=[0,1,1,2]): # Pass a list
from itertools import permutations as per
uniq_set = set()
for byt_grp in per(x, 4):
if byt_grp not in uniq_set:
yield byt_grp
uniq_set.update([byt_grp])
print uniq_set
for i in dont_repeat(): print i
(0, 1, 1, 2)
(0, 1, 2, 1)
(0, 2, 1, 1)
(1, 0, 1, 2)
(1, 0, 2, 1)
(1, 1, 0, 2)
(1, 1, 2, 0)
(1, 2, 0, 1)
(1, 2, 1, 0)
(2, 0, 1, 1)
(2, 1, 0, 1)
(2, 1, 1, 0)
set([(0, 1, 1, 2), (1, 0, 1, 2), (2, 1, 0, 1), (1, 2, 0, 1), (0, 1, 2, 1), (0, 2, 1, 1), (1, 1, 2, 0), (1, 2, 1, 0), (2, 1, 1, 0), (1, 0, 2, 1), (2, 0, 1, 1), (1, 1, 0, 2)])
Fondamentalement, créez un ensemble et continuez à l'ajouter. Mieux que de faire des listes, etc. qui prennent trop de mémoire .. J'espère que cela aidera la prochaine personne à regarder :-) Commentez l'ensemble 'update' dans la fonction pour voir la différence.
Vous pouvez créer une fonction qui utilise collections.Counter
pour obtenir des éléments uniques et leur nombre à partir de la séquence donnée, et utilise itertools.combinations
pour sélectionner des combinaisons d'index pour chaque élément unique dans chaque appel récursif, et mapper les index sur une liste lorsque tous les index sont sélectionnés. :
from collections import Counter
from itertools import combinations
def unique_permutations(seq):
def index_permutations(counts, index_pool):
if not counts:
yield {}
return
(item, count), *rest = counts.items()
rest = dict(rest)
for indices in combinations(index_pool, count):
mapping = dict.fromkeys(indices, item)
for others in index_permutations(rest, index_pool.difference(indices)):
yield {**mapping, **others}
indices = set(range(len(seq)))
for mapping in index_permutations(Counter(seq), indices):
yield [mapping[i] for i in indices]
pour que [''.join(i) for i in unique_permutations('moon')]
retourne:
['moon', 'mono', 'mnoo', 'omon', 'omno', 'nmoo', 'oomn', 'onmo', 'nomo', 'oonm', 'onom', 'noom']
Voici une solution récursive au problème.
def permutation(num_array):
res=[]
if len(num_array) <= 1:
return [num_array]
for num in set(num_array):
temp_array = num_array.copy()
temp_array.remove(num)
res += [[num] + perm for perm in permutation(temp_array)]
return res
arr=[1,2,2]
print(permutation(arr))
Qu'en est-il de
np.unique(itertools.permutations([1, 1, 1]))
Le problème est que les permutations sont maintenant des lignes d'un tableau Numpy, utilisant ainsi plus de mémoire, mais vous pouvez les parcourir comme auparavant
perms = np.unique(itertools.permutations([1, 1, 1]))
for p in perms:
print p
Je suis tombé sur ce problème l’autre jour alors que je travaillais sur un de mes problèmes. J'aime l'approche de Luka Rahne, mais je pensais que l'utilisation de la classe Counter dans la bibliothèque de collections semblait être une amélioration modeste. Voici mon code:
def unique_permutations(elements):
"Returns a list of lists; each sublist is a unique permutations of elements."
ctr = collections.Counter(elements)
# Base case with one element: just return the element
if len(ctr.keys())==1 and ctr[ctr.keys()[0]] == 1:
return [[ctr.keys()[0]]]
perms = []
# For each counter key, find the unique permutations of the set with
# one member of that key removed, and append the key to the front of
# each of those permutations.
for k in ctr.keys():
ctr_k = ctr.copy()
ctr_k[k] -= 1
if ctr_k[k]==0:
ctr_k.pop(k)
perms_k = [[k] + p for p in unique_permutations(ctr_k)]
perms.extend(perms_k)
return perms
Ce code renvoie chaque permutation sous forme de liste. Si vous lui donnez une chaîne, il vous donnera une liste de permutations, chacune étant une liste de caractères. Si vous souhaitez plutôt que la sortie soit une liste de chaînes (par exemple, si vous êtes une personne terrible et que vous voulez abuser de mon code pour vous aider à tricher dans Scrabble), procédez comme suit:
[''.join(perm) for perm in unique_permutations('abunchofletters')]
La meilleure solution à ce problème que j'ai vue utilise "l'algorithme L" de Knuth (comme noté précédemment par Gerrat dans les commentaires au message original):
http://stackoverflow.com/questions/12836385/how-can-i-interleave-or-create-unique-permutations-of-two-stings-without-recurs/12837695
Quelques timings:
Tri [1]*12+[0]*12
(2 704 156 permutations uniques):
Algorithme L → 2.43 s
La solution de Luke Rahne → 8.56 sscipy.multiset_permutations()
→ 16.8 s
Je suis venu avec une implémentation très appropriée en utilisant itertools.product (c'est une implémentation où vous voulez toutes les combinaisons
unique_perm_list = [''.join(p) for p in itertools.product(['0', '1'], repeat = X) if ''.join(p).count() == somenumber]
c'est essentiellement une combinaison (n sur k) avec n = X et somenumber = k itertools.product () itère de k = 0 à k = X le filtrage ultérieur avec count garantit que seules les permutations avec le nombre correct de uns sont jetés dans une liste. vous pouvez facilement voir que cela fonctionne lorsque vous calculez n sur k et le comparez à la valeur len (unique_perm_list)