J'essaie d'utiliser itertools.permutations () pour renvoyer toutes les permutations de la chaîne et ne renvoyer que celles qui sont membres d'un ensemble de mots.
import itertools
def permutations_in_dict(string, words):
'''
Parameters
----------
string : {str}
words : {set}
Returns
-------
list : {list} of {str}
Example
-------
>>> permutations_in_dict('act', {'cat', 'rat', 'dog', 'act'})
['act', 'cat']
'''
Ma solution actuelle fonctionne très bien dans le terminal mais n'a pas réussi à passer le test ...
return list(set([''.join(p) for p in itertools.permutations(string)]) & words)
Toute aide serait appréciée.
Vous pouvez simplement utiliser collections.Counter()
pour comparer le words
au string
sans créer tout permutations
(cela explose avec la longueur de la chaîne):
from collections import Counter
def permutations_in_dict(string, words):
c = Counter(string)
return [w for w in words if c == Counter(w)]
>>> permutations_in_dict('act', {'cat', 'rat', 'dog', 'act'})
['cat', 'act']
Remarque: set
s ne sont pas classés, donc si vous avez besoin d'une commande spécifique, vous devrez peut-être trier le résultat, par exemple return sorted(...)
Le problème que vous résolvez est mieux décrit comme un test pour les correspondances anagram .
solution traditionnelle consiste à trier la chaîne cible, à trier la chaîne candidate et à tester l'égalité.
>>> def permutations_in_dict(string, words):
target = sorted(string)
return sorted(Word for Word in words if sorted(Word) == target)
>>> permutations_in_dict('act', {'cat', 'rat', 'dog', 'act'})
['act', 'cat']
Une autre approche consiste à utiliser collections.Counter () pour effectuer un test d'égalité multiset . Ceci est algorithmiquement supérieur à la solution de tri (O(n)
contre O(n log n)
) mais a tendance à perdre à moins que la taille des chaînes soit grande (en raison du coût de hachage de tous les caractères).
>>> def permutations_in_dict(string, words):
target = Counter(string)
return sorted(Word for Word in words if Counter(Word) == target)
>>> permutations_in_dict('act', {'cat', 'rat', 'dog', 'act'})
['act', 'cat']
Une signature d'anagramme unique ou hachage parfait peut être construite en multipliant les nombres premiers correspondant à chaque caractère possible dans une chaîne.
La propriété commutative de multiplication garantit que la valeur de hachage sera invariante pour toute permutation d'une seule chaîne. L'unicité de la valeur de hachage est garantie par le théorème fondamental de l'arithmétique (également connu sous le nom de théorème de factorisation premier unique).
>>> from operator import mul
>>> primes = [2, 3, 5, 7, 11]
>>> primes += [p for p in range(13, 1620) if all(pow(b, p-1, p) == 1 for b in (5, 11))]
>>> anagram_hash = lambda s: reduce(mul, (primes[ord(c)] for c in s))
>>> def permutations_in_dict(string, words):
target = anagram_hash(string)
return sorted(Word for Word in words if anagram_hash(Word) == target)
>>> permutations_in_dict('act', {'cat', 'rat', 'dog', 'act'})
['act', 'cat']
La recherche par permutations sur la chaîne cible en utilisant itertools.permutations () est raisonnable lorsque la chaîne est petite (générer des permutations sur a n = la chaîne de longueur génère n candidats factoriels).
La bonne nouvelle est que lorsque n est petit et que le nombre de mots est grand, cette approche fonctionne très rapidement (car le test d'appartenance à l'ensemble est O (1) ):
>>> from itertools import permutations
>>> def permutations_in_dict(string, words):
perms = set(map(''.join, permutations(string)))
return sorted(Word for Word in words if Word in perms)
>>> permutations_in_dict('act', {'cat', 'rat', 'dog', 'act'})
['act', 'cat']
Comme l'OP l'a supposé, la boucle de recherche pure python peut être accélérée à la vitesse c en utilisant set.intersection () =:
>>> def permutations_in_dict(string, words):
perms = set(map(''.join, permutations(string)))
return sorted(words & perms)
>>> permutations_in_dict('act', {'cat', 'rat', 'dog', 'act'})
['act', 'cat']
La meilleure solution dépend de la longueur de chaîne et de la longueur de mots. Les horaires indiqueront ce qui convient le mieux à un problème particulier.
Voici quelques timings comparatifs pour les différentes approches utilisant deux tailles de chaîne différentes:
Timings with string_size=5 and words_size=1000000
-------------------------------------------------
0.01406 match_sort
0.06827 match_multiset
0.02167 match_perfect_hash
0.00224 match_permutations
0.00013 match_permutations_set
Timings with string_size=20 and words_size=1000000
--------------------------------------------------
2.19771 match_sort
8.38644 match_multiset
4.22723 match_perfect_hash
<takes "forever"> match_permutations
<takes "forever"> match_permutations_set
Les résultats indiquent que pour les petites chaînes, l'approche la plus rapide recherche les permutations sur la chaîne cible en utilisant set-intersection.
Pour les chaînes plus grandes, l'approche la plus rapide est la solution traditionnelle de tri et de comparaison.
J'espère que vous avez trouvé cette petite étude algorithmique aussi intéressante que moi. Les plats à emporter sont:
FWIW, voici une configuration de test que j'ai utilisée pour exécuter les timings comparatifs:
from collections import Counter
from itertools import permutations
from string import letters
from random import choice
from operator import mul
from time import time
def match_sort(string, words):
target = sorted(string)
return sorted(Word for Word in words if sorted(Word) == target)
def match_multiset(string, words):
target = Counter(string)
return sorted(Word for Word in words if Counter(Word) == target)
primes = [2, 3, 5, 7, 11]
primes += [p for p in range(13, 1620) if all(pow(b, p-1, p) == 1 for b in (5, 11))]
anagram_hash = lambda s: reduce(mul, (primes[ord(c)] for c in s))
def match_perfect_hash(string, words):
target = anagram_hash(string)
return sorted(Word for Word in words if anagram_hash(Word) == target)
def match_permutations(string, words):
perms = set(map(''.join, permutations(string)))
return sorted(Word for Word in words if Word in perms)
def match_permutations_set(string, words):
perms = set(map(''.join, permutations(string)))
return sorted(words & perms)
string_size = 5
words_size = 1000000
population = letters[: string_size+2]
words = set()
for i in range(words_size):
Word = ''.join([choice(population) for i in range(string_size)])
words.add(Word)
string = Word # Arbitrarily search use the last Word as the target
print 'Timings with string_size=%d and words_size=%d' % (string_size, words_size)
for func in (match_sort, match_multiset, match_perfect_hash, match_permutations, match_permutations_set):
start = time()
func(string, words)
end = time()
print '%-10.5f %s' % (end - start, func.__name__)
Apparemment, vous vous attendez à ce que la sortie soit triée par ordre alphabétique, donc cela devrait faire:
return sorted(set(''.join(p) for p in itertools.permutations(string)) & words)
Essayez cette solution
list(map("".join, itertools.permutations('act')))
['act', 'atc', 'cat', 'cta', 'tac', 'tca']
On peut l'appeler listA
listA = list(map("".join, itertools.permutations('act')))
Votre liste est ListB
listB = ['cat', 'rat', 'dog', 'act']
Utilisez ensuite l'intersection définie
list(set(listA) & set(listB))
['cat', 'act']