Je suppose que vous pouvez classer cela comme un problème de style Scrabble, mais cela a commencé à cause d'un ami mentionnant le compte à rebours du Quiz TV britannique. Divers rounds dans le spectacle impliquent les concurrents présentant un ensemble de lettres broulé et ils doivent trouver le mot le plus long qu'ils puissent. Celui que mon ami a mentionné était "Raepkwaen".
En ordre assez court, j'ai fouetté quelque chose à Python pour gérer ce problème, en utilisant Pyencher pour gérer les loisirs du dictionnaire, mais je remarque que cela ne peut vraiment pas échouer Tout ça bien. P>
Voici ce que j'ai actuellement: P>
#!/usr/bin/python from itertools import permutations import enchant from sys import argv def find_longest(origin): s = enchant.Dict("en_US") for i in range(len(origin),0,-1): print "Checking against words of length %d" % i pool = permutations(origin,i) for comb in pool: word = ''.join(comb) if s.check(word): return word return "" if (__name__)== '__main__': result = find_longest(argv[1]) print result
10 Réponses :
Vous voulez éviter de faire la permutation. Vous pouvez compter le nombre de fois qu'un personnage apparaît dans les deux chaînes (la chaîne d'origine et celle du dictionnaire). Rejeter tous les mots du dictionnaire où la fréquence des caractères n'est pas la même. p>
Pour vérifier un mot dans le dictionnaire, vous devrez compter les caractères au plus de temps maximum (26, N). p>
Ouais. C'est la voie, à l'exception de votre recherche de fréquence supérieure ou égale dans l'original. Compte tenu d'environ 1 000 000 mots anglais, nous examinons probablement environ 50-60 Mo de mémoire pour le dictionnaire et environ environ 27 millions de calculs. Joli! Les structures de données plus complexes et le pré-traitement peuvent améliorer encore plus encore.
@Twooster Pourquoi mettre le dictionnaire en mémoire? C'est une recherche linéaire, vous pouvez le faire en numérisant un fichier.
Une simple optimisation serait de trier le fichier de dictionnaire par la longueur de mot (O (nlogn) mais uniquement une fois) et commencez une recherche sur des mots au plus aussi longtemps que le fouillage.
J'ai écrit un programme comme celui-ci une fois. La chose la plus facile est de charger tout le dictionnaire dans un dict python, où chaque mot est saisi par ses lettres triées. Cela vous permet de rechercher un permis d'admission dans O (1): si trié (mot) dans dict code>. Mais pour différentes longueurs, vous devez faire des combinaisons, à l'aide d'un algorithme naïf ceci est O (2 ^ N) même s'il peut y avoir une meilleure façon.
Ensuite, lorsque vous recherchez un ensemble de lettres donné: p>
Vous auriez besoin de le faire séparément pour chaque longueur de mot. p>
Edit: Disons que vous recherchez toutes les combinaisons uniques des lettres triées de la longueur du mot cible ( plage (LEN (Lettres), 0, -1) CODE>) P>
À ce stade, votre trie est la représentation de tous les mots de votre dictionnaire pouvant être construit à partir de votre sac de lettres. P>
EDIT: Vous pouvez également utiliser un DAGW (graphique de mot acyclique dirigé) qui aura moins sommets. Bien que je ne l'ai pas lu, cet article Wikipedia a un lien sur Le programme au Scrabble le plus rapide du monde . P>
Lorsque vous recherchez des mots de plus de 10 lettres, vous pouvez essayer de parcourir des mots (je pense qu'il n'y a pas tant de mots avec 10 lettres de plus de 10 lettres et de vérifier que vous avez besoin de lettres dans votre ensemble. p>
problème est que vous devez trouver tous ces len (mot)> = 10 mots en premier. p>
Alors, qu'est-ce que je ferais: Lorsque la lecture du dictionnaire a divisé les mots en 2 catégories: Shorts et Longs. Vous pouvez traiter des shorts en itérant sur chaque permutation possible. Que vous pouvez traiter des longs en itération de dessus et en le vérifiant, ils sont possibles. P>
Bien sûr, il existe de nombreuses optimisations possibles aux deux chemins. P>
implémentation de Jeroen Coupé idée de Sa réponse avec des lettres comptez: sortie (pour mes petits 58000 mots dict): p> C'est une implémentation simple sans optimisations. P> Li>
update strong> p> au cas où nous devions trouver un mot une seule fois, et nous avons le dictionnaire avec des mots triés par longueur, par ex. Par ce script: P>
mots_list.txt code> - peut être
/ USR / Share / dict / mots code> sur Linux. P> LI>
ul>
from collections import Counter
import sys
def check_same_letters(origin_map, word):
new_map = Counter(word)
return all(new_map[let] <= origin_map[let] for let in word)
def iter_longest_from_file(origin, file_path, min_length=1):
origin_map = Counter(origin)
origin_len = len(origin)
with open(file_path) as f:
for line in f:
word = line.strip()
if len(word) > origin_len:
continue
if len(word) < min_length:
return
if check_same_letters(origin_map, word):
yield word
def find_longest_from_file(origin, file_path):
return iter_longest_from_file(origin, file_path).next()
if __name__ == '__main__':
origin = sys.argv[1] if len(sys.argv) > 1 else 'abcdefghijklmnopqrstuvwxyz'
print find_longest_from_file(origin, 'words_by_len.txt')
Ceci est similaire à un problème d'anagramme que j'ai travaillé auparavant. J'ai résolu que, en utilisant des nombres premiers pour représenter chaque lettre. Le produit des lettres pour chaque mot produit un numéro. Pour déterminer si un ensemble donné de caractères d'entrée suffit pour faire un travail, divisez simplement le produit du caractère d'entrée par le produit pour le numéro que vous souhaitez vérifier. S'il n'y a pas de reste, les caractères d'entrée sont suffisants. Je l'ai mis en œuvre ci-dessous. La sortie est la suivante:
import sys def nextprime(x): while True: x += 1 for pot_fac in range(2,x): if x % pot_fac == 0: break else: return x def prime_generator(): '''Returns a generator that produces the next largest prime as compared to the one returned from this function the last time it was called. The first time it is called it will return 2.''' lastprime = 1 while True: lastprime = nextprime(lastprime) yield lastprime # Assign prime numbers to each lower case letter gen = prime_generator() primes = dict( [ (chr(x),gen.next()) for x in range(ord('a'),ord('z')+1) ] ) product = lambda x: reduce( lambda m,n: m*n, x, 1 ) make_key = lambda x: product( [ primes[y] for y in x ] ) try: words = open('words').readlines() words = [ ''.join( [ c for c in x.lower() \ if ord('a') <= ord(c) <= ord('z') ] ) \ for x in words ] for x in words: try: make_key(x) except: print x raise except IOError: words = [ 'reawaken','awaken','enwrap','weaken','weaker', ] words = dict( ( (make_key(x),x,) for x in words ) ) inputs = sys.argv[1:] if sys.argv[1:] else [ 'raepkwaen', ] for input in inputs: input_key = make_key(input) results = [ words[x] for x in words if input_key % x == 0 ] result = reversed(sorted(results, key=len)).next() print input,'--> ',result
Je suis assez certain de votre NextPrime () code> Fonction pourrait
pour pot_fac dans xrange (2, x / 2) code> ou même
math.sqrt (x) code>
@Droogans - Oui, de nombreuses optimisations privilégiées sont disponibles. Puisque je ne prends que 26, je pourrais les énumérer. J'allais pour la lisibilité plutôt que la vitesse pour les premiers initialisations. La meilleure liste de pot_fac à utiliser serait tous les nombres premiers précédemment renvoyés au sol (SQRT (X)).
dawg (graphique de mot acyclique dirigé) Mark Wutka était assez gentille pour fournir du code Pascal ici. P>
J'ai commencé hier soir peu après vous avez posé la question, mais n'a pas obtenu autour de polir jusqu'à ce moment. Ce fut ma solution, qui est essentiellement une structure arborescente modifiée, que je ne savais pas jusqu'à aujourd'hui
>>> sorted(tree.find_max_word('RAEPKWAEN'), key=len)[-5:] ['wakener', 'rewaken', 'reawake', 'reawaken', 'awakener']
Une autre approche, semblable à la réponse de @ Market, est de précomputer un «bitmask» pour chaque mot dans le dictionnaire. Le bit 0 est défini si le mot contient au moins un A, le bit 1 est réglé s'il contient au moins un B, et ainsi sur le bit jusqu'à 25 pour Z. P>
Si vous souhaitez rechercher tous les mots du dictionnaire qui pourraient être composés d'une combinaison de lettres, vous commencez en formant le bitmask pour la collecte de lettres. Vous pouvez ensuite filtrer tous les mots qui utilisent d'autres lettres en vérifiant si L'avantage de cette approche est que les opérations bitwises sont rapides. La grande majorité des mots du dictionnaire utiliseront au moins une des 17 lettres ou plus qui ne sont pas dans la collection donnée et vous pourrez les négliger rapidement. Cependant, pour la minorité de mots qui le rendent à travers le filtre, il y a un chèque supplémentaire que vous devez encore faire. Vous devez toujours vérifier que les mots n'utilisent pas les lettres plus souvent qu'ils n'apparaissent dans la collection. Par exemple, le mot «affaissé» doit être interdit car il a trois 'E's, alors qu'il n'y en a que deux dans la collection de lettres Raepkwaen. L'approche bitwise seule ne fera pas filtrer ce mot car chaque lettre du mot apparaît dans la collection. P> wordbitmask & ~ lettersbitmask code> est zéro. Si tel est zéro, le mot utilise uniquement des lettres disponibles dans la collection, et pourrait donc être valide. S'il s'agit de non-zéro, il utilise une lettre non disponible dans la collection et n'est donc pas autorisée. P>
Si vous avez un fichier texte avec des mots triés. Ce code fait simplement le calcul:
Je ne suis pas sûr à quel point l'enchanteur est efficace. Mais est-il possible de trouver une liste avec tous les mots n i> caractères longtemps? Si tel est le cas, vous pouvez charger cette liste en mémoire et faire un dans i> au lieu d'une enchantée.Check. Je suppose que c'est rapide pour les mots longs. Mais la liste sera trop longue pour les mots courts.
@Willian, postez ce commentaire comme une réponse afin de vérifier votre approche: Secteur de capture de permutations dans le dictionnaire, vérifiez toutes les lettres de mots de dictionnaire en lettres valides.
Fair Point @willian, j'avais également écrit cela avec des liaisons Python-Aspell. On dirait que Enchanter est un goulot d'étranglement, la version Aspell prend beaucoup moins de temps (environ la moitié), mais il va toujours prendre beaucoup de temps de force brute! Merci à tout le monde pour vos réponses, des idées intéressantes là-bas. Je vais essayer de les mettre en œuvre et voir quelles différences de vitesse nous verrons.
Raepkwaen? Où ai-je vu cette collection de lettres avant? BBC.CO.UK/NEWS/ENTERTERY-ARTS-16627873
Cette question a généré des très bonnes réponses. +1