Il s'agit d'un jeu de mots à partir d'un jeu de cartes d'activités pour les enfants. Sous les règles se trouve le code pour trouver le meilleur triplet en utilisant / usr / share / dict / words. Je pensais que c'était un problème d'optimisation intéressant et je me demandais si les gens pouvaient trouver des améliorations.
Règles
- Choisissez une lettre dans chacun des ensembles ci-dessous.
- Choisissez un mot en utilisant les lettres choisies (et toutes les autres).
- Marquez le mot.
- Chaque lettre de l'ensemble choisi obtient le numéro indiqué avec l'ensemble (répétitions incluses).
AEIOU
compter 0- Toutes les autres lettres sont -2
- Répétez les étapes 1 à 3 ci-dessus (pas de réutilisation des lettres à l'étape 1) deux fois de plus.
- Le score final est la somme des scores de trois mots.
Ensembles
(set 1 marque 1 point, set 2 marque 2 points, etc.)
- LTN
- RDS
- GBM
- CHP
- FWV
- YKJ
- QXZ
Code:
from itertools import permutations
import numpy as np
points = {'LTN' : 1,
'RDS' : 2,
'GBM' : 3,
'CHP' : 4,
'FWV' : 5,
'YKJ' : 6,
'QXZ' : 7}
def tonum(word):
word_array = np.zeros(26, dtype=np.int)
for l in word:
word_array[ord(l) - ord('A')] += 1
return word_array.reshape((26, 1))
def to_score_array(letters):
score_array = np.zeros(26, dtype=np.int) - 2
for v in 'AEIOU':
score_array[ord(v) - ord('A')] = 0
for idx, l in enumerate(letters):
score_array[ord(l) - ord('A')] = idx + 1
return np.matrix(score_array.reshape(1, 26))
def find_best_words():
wlist = [l.strip().upper() for l in open('/usr/share/dict/words') if l[0].lower() == l[0]]
wlist = [l for l in wlist if len(l) > 4]
orig = [l for l in wlist]
for rep in 'AEIOU':
wlist = [l.replace(rep, '') for l in wlist]
wlist = np.hstack([tonum(w) for w in wlist])
best = 0
ct = 0
bestwords = ()
for c1 in ['LTN']:
for c2 in permutations('RDS'):
for c3 in permutations('GBM'):
for c4 in permutations('CHP'):
for c5 in permutations('FWV'):
for c6 in permutations('YJK'):
for c7 in permutations('QZX'):
vals = [to_score_array(''.join(s)) for s in zip(c1, c2, c3, c4, c5, c6, c7)]
ct += 1
print ct, 6**6
scores1 = (vals[0] * wlist).A.flatten()
scores2 = (vals[1] * wlist).A.flatten()
scores3 = (vals[2] * wlist).A.flatten()
m1 = max(scores1)
m2 = max(scores2)
m3 = max(scores3)
if m1 + m2 + m3 > best:
print orig[scores1.argmax()], orig[scores2.argmax()], orig[scores3.argmax()], m1 + m2 + m3
best = m1 + m2 + m3
bestwords = (orig[scores1.argmax()], orig[scores2.argmax()], orig[scores3.argmax()])
return bestwords, best
if __name__ == '__main__':
import timeit
print timeit.timeit('print find_best_words()', 'from __main__ import find_best_words', number=1)
La version matricielle est ce que j'ai trouvé après avoir écrit une en python pur (en utilisant des dictionnaires et en marquant chaque mot indépendamment), et une autre en numpy mais en utilisant l'indexation plutôt que la multiplication matricielle.
La prochaine optimisation serait de supprimer entièrement les voyelles de la notation (et d'utiliser une ord()
fonction modifiée ), mais je me demande s'il existe des approches encore plus rapides.
EDIT : code timeit.timeit ajouté
EDIT : J'ajoute une prime, que je donnerai à l'amélioration que j'aime le plus (ou éventuellement à plusieurs réponses, mais je devrai accumuler un peu plus de réputation si c'est le cas).
Réponses:
En utilisant l'idée de Keith de précalculer le meilleur score possible pour chaque mot, j'ai réussi à réduire le temps d'exécution à environ 0,7 seconde sur mon ordinateur (en utilisant une liste de 75 288 mots).
L'astuce consiste à parcourir les combinaisons de mots à jouer au lieu de toutes les combinaisons de lettres choisies. Nous pouvons ignorer toutes les combinaisons de mots sauf quelques-unes (203 en utilisant ma liste de mots) car ils ne peuvent pas obtenir un score plus élevé que celui que nous avons déjà trouvé. Presque tout le temps d'exécution est consacré au précalcul des scores de mots.
Python 2.7:
Cela renvoie la solution
['KNICKKNACK', 'RAZZMATAZZ', 'POLYSYLLABLES']
avec un score de 95. Avec les mots de la solution de Keith ajoutés à la liste de mots, j'obtiens le même résultat que lui. Avec la "xylopyrographie" de thouis ajoutée, j'obtiens['XYLOPYROGRAPHY', 'KNICKKNACKS', 'RAZZMATAZZ']
un score de 105.la source
Voici une idée - vous pouvez éviter de vérifier beaucoup de mots en remarquant que la plupart des mots ont des scores terribles. Disons que vous avez trouvé un jeu de score assez bon qui vous rapporte 50 points. Ensuite, tout jeu avec plus de 50 points doit avoir un mot d'au moins ceil (51/3) = 17 points. Ainsi, tout mot qui ne peut pas générer 17 points peut être ignoré.
Voici un code qui fait ce qui précède. Nous calculons le meilleur score possible pour chaque mot du dictionnaire et le stockons dans un tableau indexé par score. Ensuite, nous utilisons ce tableau pour vérifier uniquement les mots qui ont le score minimum requis.
Le score minimum atteint rapidement 100, ce qui signifie que nous n'avons qu'à prendre en compte plus de 33 points, ce qui représente une très petite fraction du total global (mon
/usr/share/dict/words
compte 208662 mots valides, dont seulement 1723 sont 33+ points = 0,8%). Fonctionne en une demi-heure environ sur ma machine et génère:la source