Este é um jogo de palavras de um conjunto de cartões de atividade para crianças. Abaixo das regras está o código para encontrar o melhor tripleto usando / usr / share / dict / words. Eu pensei que era um problema interessante de otimização e estou me perguntando se as pessoas podem encontrar melhorias.
Regras
- Escolha uma letra de cada um dos conjuntos abaixo.
- Escolha uma palavra usando as letras escolhidas (e outras).
- Marque a palavra.
- Cada letra do conjunto escolhido obtém o número mostrado com o conjunto (repetições incluídas).
AEIOU
contagem 0- Todas as outras letras são -2
- Repita as etapas 1 a 3 acima (sem reutilizar letras na etapa 1) mais duas vezes.
- Pontuação final é a soma das pontuações de três palavras.
Conjuntos
(definir 1 pontuação 1 ponto, definir 2 pontuação 2 pontos etc.)
- LTN
- RDS
- GBM
- CHP
- FWV
- YKJ
- QXZ
Código:
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)
A versão matricial é a que eu criei depois de escrever uma em python puro (usando dicionários e marcando cada palavra independentemente) e outra em numpy, mas usando indexação em vez de multiplicação de matrizes.
A próxima otimização seria remover completamente as vogais da pontuação (e usar uma ord()
função modificada ), mas me pergunto se existem abordagens ainda mais rápidas.
EDIT : adicionado código timeit.timeit
EDIT : Estou adicionando uma recompensa, que darei a qualquer melhoria que eu mais gostar (ou possivelmente várias respostas, mas terei que acumular mais reputação, se for esse o caso).
fonte
Respostas:
Usando a idéia de Keith de pré-computar a melhor pontuação possível para cada palavra, consegui reduzir o tempo de execução para cerca de 0,7 segundos no meu computador (usando uma lista de 75.288 palavras).
O truque é passar pelas combinações de palavras a serem reproduzidas, em vez de todas as combinações de letras escolhidas. Podemos ignorar todas, exceto algumas combinações de palavras (203 usando minha lista de palavras), porque elas não conseguem uma pontuação maior do que a que já encontramos. Quase todo o tempo de execução é gasto na pré-computação de pontuações de palavras.
Python 2.7:
Isso retorna a solução
['KNICKKNACK', 'RAZZMATAZZ', 'POLYSYLLABLES']
com uma pontuação de 95. Com as palavras da solução de Keith adicionadas à lista de palavras, obtenho o mesmo resultado que ele. Com a "xilopirografia" de thyis adicionada, recebo['XYLOPYROGRAPHY', 'KNICKKNACKS', 'RAZZMATAZZ']
uma pontuação de 105.fonte
Aqui está uma idéia - você pode evitar a verificação de muitas palavras observando que a maioria das palavras tem pontuações horríveis. Digamos que você tenha encontrado uma jogada de pontuação muito boa, com 50 pontos. Então, qualquer jogada com mais de 50 pontos deve ter uma palavra de pelo menos teto (51/3) = 17 pontos. Portanto, qualquer palavra que não possa gerar 17 pontos pode ser ignorada.
Aqui está um código que faz o acima. Calculamos a melhor pontuação possível para cada palavra no dicionário e a armazenamos em uma matriz indexada por pontuação. Em seguida, usamos essa matriz para verificar apenas as palavras com a pontuação mínima exigida.
A pontuação mínima chega rapidamente a 100, o que significa que precisamos considerar apenas 33+ palavras em pontos, que é uma fração muito pequena do total geral (a minha
/usr/share/dict/words
possui 208662 palavras válidas, apenas 1723 das quais 33+ são pontos = 0,8%). É executado em cerca de meia hora na minha máquina e gera:fonte