Это игра в слова из набора игровых карточек для детей. Ниже правил приведен код для поиска лучшего триплета с использованием / usr / share / dict / words. Я подумал, что это интересная проблема оптимизации, и мне интересно, могут ли люди найти улучшения.
правила
- Выберите одну букву из каждого набора ниже.
- Выберите слово, используя выбранные буквы (и любые другие).
- Оценка слова.
- Каждая буква из выбранного набора получает номер, показанный вместе с набором (повторы включены).
AEIOU
считать 0- Все остальные буквы -2
- Повторите шаги 1-3 выше (без повторного использования букв на шаге 1) еще дважды.
- Окончательный результат - это сумма трех слов.
наборы
(установить 1 балл 1 балл, установить 2 балла 2 балла и т. д.)
- LTN
- RDS
- GBM
- CHP
- FWV
- YKJ
- QXZ
Код:
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)
Матричная версия - это то, что я придумал после написания одной на чистом Python (используя словари и оценивая каждое слово независимо), а другой - на numpy, но используя индексирование, а не матричное умножение.
Следующая оптимизация будет состоять в том, чтобы полностью удалить гласные из оценки (и использовать модифицированную ord()
функцию), но мне интересно, есть ли еще более быстрые подходы.
РЕДАКТИРОВАТЬ : добавлен код timeit.timeit
РЕДАКТИРОВАТЬ : я добавляю вознаграждение, которое я дам для любого улучшения, которое мне больше всего нравится (или, возможно, несколько ответов, но мне придется набрать еще немного репутации, если это так).
источник
Ответы:
Используя идею Кейта о предварительном вычислении наилучшего результата для каждого слова, мне удалось сократить время выполнения на моем компьютере примерно до 0,7 секунды (используя список из 75 288 слов).
Хитрость заключается в том, чтобы проходить комбинации слов, которые нужно сыграть, вместо всех выбранных комбинаций букв. Мы можем игнорировать все, кроме нескольких комбинаций слов (203, используя мой список слов), потому что они не могут получить более высокий балл, чем мы уже нашли. Почти все время выполнения тратится на предварительные вычисления слов.
Python 2.7:
Это возвращает решение
['KNICKKNACK', 'RAZZMATAZZ', 'POLYSYLLABLES']
со счетом 95. Со словами из решения Кейта, добавленными в список слов, я получаю тот же результат, что и он. С добавленной вами «ксилопирографией» я получил['XYLOPYROGRAPHY', 'KNICKKNACKS', 'RAZZMATAZZ']
105 баллов.источник
Вот идея - вы можете избежать проверки большого количества слов, заметив, что большинство слов имеют ужасные оценки. Скажем, вы нашли довольно хорошую игру, которая приносит вам 50 очков. Тогда любая игра с более чем 50 очками должна иметь слово не менее ceil (51/3) = 17 очков. Поэтому любое слово, которое не может дать 17 очков, можно игнорировать.
Вот код, который делает выше. Мы вычисляем наилучшую возможную оценку для каждого слова в словаре и сохраняем его в массиве, индексированном по оценке. Затем мы используем этот массив для проверки только тех слов, которые имеют требуемый минимальный балл.
Минимальный балл быстро увеличивается до 100, что означает, что нам нужно учитывать только 33+ балльных слова, что является очень малой долей от общего количества (у меня
/usr/share/dict/words
208662 правильных слов, только 1723 из которых 33+ баллов = 0,8%). Запускается примерно на полчаса на моей машине и генерирует:источник