web-dev-qa-db-fra.com

permutations avec des valeurs uniques

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.

60
xyz-123
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 de
for g in perm_unique_helper(listunique,result_list,d-1): yield gso 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.

47
Luka Rahne

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]]
22
Bill Bell

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')
18
Steven Rumbalski

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

11
Paul Rubel

À 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,3
  • Parcourons-les: first_element commence par 1 .
    • remaining_elements sont [2,3,1] (c'est-à-dire 1,2,3,1 moins le premier)
    • Nous parcourons (récursivement) les permutations des éléments restants: (1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)
    • Pour chaque sub_permutation, nous insérons le first_element: ( 1 , 1,2,3), ( 1 , 1,3,2), ... et donnons le résultat.
  • Maintenant, nous parcourons 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)
    • Nous parcourons les permutations des éléments restants: (1, 1, 3), (1, 3, 1), (3, 1, 1)
    • Pour chaque 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.
  • Enfin, nous faisons la même chose avec first_element = 3.
10
MiniQuark

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]]
8
Little Roys

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.

3
pylang

On dirait que vous cherchez itertools.combinations () docs.python.org

list(itertools.combinations([1, 1, 1],3))
[(1, 1, 1)]
3
Fredrik Pihl

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.

2
Ashish Datta

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']
1
blhsing

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))
1
prafi

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
0
Andre Manoel

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')]
0
CCC

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 s
scipy.multiset_permutations() → 16.8 s 

0
bmorgan

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)

0
mnmldani