To gra słowna z zestawu kart aktywności dla dzieci. Poniżej zasad znajduje się kod służący do znalezienia najlepszego tripletu za pomocą / usr / share / dict / words. Myślałem, że to interesujący problem z optymalizacją i zastanawiam się, czy ludzie mogą znaleźć ulepszenia.
Zasady
- Wybierz jedną literę z każdego z poniższych zestawów.
- Wybierz słowo, używając wybranych liter (i dowolnych innych).
- Oceń słowo.
- Każda litera z wybranego zestawu otrzymuje numer pokazany wraz z zestawem (w tym powtórzenia).
AEIOU
policz 0- Wszystkie pozostałe litery to -2
- Powtórz kroki 1-3 powyżej (bez ponownego użycia liter w kroku 1) jeszcze dwa razy.
- Wynik końcowy jest sumą wyników trzech słów.
Zestawy
(zestaw 1 zdobywa 1 punkt, zestaw 2 zdobywa 2 punkty itp.)
- LTN
- RDS
- GBM
- CHP
- FWV
- YKJ
- QXZ
Kod:
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)
Wersję macierzową wymyśliłem po napisaniu jednego w czystym pythonie (używając słowników i oceniania każdego słowa niezależnie), a drugiego w numpy, ale używając indeksowania zamiast mnożenia macierzy.
Następną optymalizacją byłoby całkowite usunięcie samogłosek z punktacji (i użycie zmodyfikowanej ord()
funkcji), ale zastanawiam się, czy są jeszcze szybsze podejścia.
EDYCJA : dodano kod timeit.timeit
EDYCJA : Dodam nagrodę, którą dam każdemu ulepszeniu, które najbardziej mi się podoba (lub ewentualnie wielu odpowiedziom, ale w takim przypadku będę musiał zyskać trochę reputacji).
źródło
Odpowiedzi:
Korzystając z pomysłu Keitha, aby wstępnie obliczyć najlepszy możliwy wynik dla każdego słowa, udało mi się skrócić czas wykonywania do około 0,7 sekundy na moim komputerze (używając listy 75 288 słów).
Sztuczka polega na przejrzeniu kombinacji słów, które mają być odtwarzane, zamiast wszystkich kombinacji wybranych liter. Możemy zignorować wszystkie oprócz kilku kombinacji słów (203 przy użyciu mojej listy słów), ponieważ nie mogą uzyskać wyższego wyniku niż my już znaleźliśmy. Prawie cały czas wykonywania jest spędzany na obliczaniu wyników słów.
Python 2.7:
To zwraca rozwiązanie
['KNICKKNACK', 'RAZZMATAZZ', 'POLYSYLLABLES']
z wynikiem 95. Po dodaniu słów z rozwiązania Keitha do listy słów otrzymuję taki sam wynik jak on. Po dodaniu „ksylopirografii” Thouisa uzyskałem['XYLOPYROGRAPHY', 'KNICKKNACKS', 'RAZZMATAZZ']
wynik 105.źródło
Oto pomysł - możesz uniknąć sprawdzania wielu słów, zauważając, że większość słów ma okropne wyniki. Powiedzmy, że znalazłeś całkiem niezłą grę punktową, która daje ci 50 punktów. Wtedy każda gra z więcej niż 50 punktami musi mieć słowo co najmniej ceil (51/3) = 17 punktów. Dlatego każde słowo, które nie może wygenerować 17 punktów, można zignorować.
Oto kod, który wykonuje powyższe czynności. Obliczamy najlepszy możliwy wynik dla każdego słowa w słowniku i przechowujemy go w tablicy indeksowanej według wyniku. Następnie używamy tej tablicy do sprawdzania tylko słów, które mają wymagany minimalny wynik.
Minimalny wynik szybko wzrasta do 100, co oznacza, że musimy wziąć pod uwagę tylko 33+ punkty słów, co stanowi bardzo mały ułamek ogólnej sumy (moje
/usr/share/dict/words
ma 208662 poprawnych słów, z których tylko 1723 to 33+ punkty = 0,8%). Działa na moim komputerze za około pół godziny i generuje:źródło