2010-03-23 139 views
17

Estoy trabajando en un problema tipo crucigrama, pero no sé cómo diseñar el algoritmo.Escribiendo un algoritmo para scrabble

Por ejemplo:

  • hay palabras como 'coche', 'manzana' en el diccionario.
  • la palabra 'aplicación' figura en el pizarrón.
  • hay letras como 'l' 'e' 'c' 'r' .... para hacer palabras.

Así que la tarea del algoritmo es hacer las palabras correctas que se almacenan en el diccionario.

aplicación -> Lapp -> leapp -> lecapp -> .... -> Lappe -> eappc -> ... -> apl -> manzana (respuesta correcta)

¿Cuál es la mejor solución para este algoritmo?

Respuesta

7

tienda su diccionario como un árbol, por ejemplo:

  * 
      | 
    +----+----+ 
    |   | 
    A*  B 
    |   | 
    +--+--+  E* 
    |  |  | 
    P  S +-+-+ 
    |  | | | 
    P* K* A E* 
    |   | 
+-+-+  +-+-+ 
| |  | | 
E L  D* N* 
| | 
A E* 
| 
L* 

Gracias paxdiablo para hacer mi árbol más legible.

Este árbol tiene las palabras a, app, appeal, apple, ask, bead, bean, be, y bee. Los nodos marcados con un asterisco indican "Si tuviera que detenerme aquí, esta sería una palabra válida" como "e" debajo de "b" para "ser".

Cuando encuentre una letra que no conoce, use un comodín (es decir, elija todos los secundarios y recurse en todas las rutas).

Dijo crucigrama, pero luego sus "letras ... para hacer palabras" parecían indicar Scrabble. Esto funcionaría para cualquiera de los dos. No es el más rápido, pero es bastante rápido.

Gracias a Andreas por recordarnos que esto se llama trie.

Si quisiera decir "la segunda letra es una P", comenzaría desde el nodo raíz y tomaría todas las ramas (que serían todas las letras del alfabeto, asumiendo que es un diccionario correcto) y luego la "P" rama y luego continuar desde allí.

+0

¿Cómo buscar este diccionario con restricciones en las letras no primarias? p.ej. la segunda letra debe ser un P? –

+5

Esto se llama árbol "trie" o prefijo. –

5

He escrito un programa de crucigramas anteriormente (críptico pero la teoría detrás de la construcción es idéntica).

Tenía una base de datos de palabras y sus pistas que podían ordenarse por tiempos utilizados (de modo que no tendería a obtener crucigramas duplicados en ejecuciones posteriores).

Lo primero que debes hacer es diseñar tus patrones (negros donde no puedes poner letras y blancos donde puedas). Intentar ajustar palabras en una cuadrícula mientras se crea el patrón sobre la marcha consume mucho tiempo y es propenso a errores. Si observa la mayoría de los crucigramas, tienden a seguir ciertas reglas para hacerlo más fácil. Las cosas son simétricas alrededor de una de las diagonales y rechaza un cuadrado de cuatro glóbulos blancos (para facilitar la tarea de seleccionar palabras adecuadas).

Una vez que tenga el patrón, , entonces comienza a encontrar palabras para colocar en él. De esta forma, sabría que "aplicación" fue start de la palabra y podrá limitar sus búsquedas a aquellas que comiencen con "app", no a cada palabra que tenga "app" en ella. Del mismo modo para las palabras donde tiene letras conocidas en cualquier posición.Es mucho más fácil ubicar palabras con letras en posiciones conocidas que evaluar esas letras en cualquier posición inicial dentro de una palabra.

El mero terminó siendo escrito en un script de shell (créalo o no) y usando el diccionario que vino de Linux como una herramienta de búsqueda de palabras. Si usted sabe que tiene una palabra de 5 letras que empiezan por "aplicación", es muy fácil de usar:

grep '^app..$' words.txt 

para obtener una lista de todas las posibilidades válidos.

Y, como se encontró cada palabra, se copió en un archivo clues.txt que contenía tanto la palabra como varias pistas posibles. El formato real era usar {count, word, clue} donde la misma palabra puede existir en múltiples líneas con una pista diferente - esto permitió canalizar grep hasta sort para que las palabras/pistas menos utilizadas flotaran hacia arriba (siempre que una palabra/se usó una pista, su recuento se incrementó, por lo que es menos probable que se use la próxima vez).

Una vez que el archivo tenía un tamaño decente, el programa lo usaría primero para ubicar las palabras y, solo si no se encontraba, revertiría al archivo de palabras (sin pistas) donde se requeriría la intervención manual.

En realidad, terminó siendo bastante bueno en el trabajo. No fue deslumbrantemente rápido, pero no tuve que generar uno cada tres segundos; esto fue para un boletín comunitario enviado una vez a la semana.


Ahora que ha cambiado la pregunta a una variante de Scrabble, en realidad es mucho más difícil.

Debe tener en cuenta las letras que tiene, las letras en el tablero y el hecho de que hay muchos más lugares que debe evaluar. Esto hace que un método de fuerza bruta sea mucho más difícil.

Lo que haría como un corte inicial sería seleccionar posibilidades (posición inicial y dirección en el tablero) elegidas al azar, luego usar el mismo algoritmo que para la variante de crucigrama anterior para ubicar todas las palabras que puedan caber allí. Luego, si tiene las letras para satisfacer esa palabra, guárdela (junto con su puntaje) en una lista.

Tenga en cuenta que debe vigilar para no interferir con otras palabras en el pizarrón.

que seguiría examinando las posibilidades hasta que uno de:

  • su lista es lo suficientemente grande como para elegir.
  • se te acabó el tiempo.
  • ha examinado suficientes posibilidades para satisfacer su nivel de competencia.

Esa última es importante: si está jugando como un principiante, no quiere examinar exhaustivamente millones de posibilidades.

A continuación, elija el mejor movimiento de su lista (o tal vez no el mejor si juega en el nivel de principiante; todo depende de qué tan buena sea la computadora).

0

Si está intentando crear un índice de palabras tal que podría intentar "resolver" (o crear) crucigramas, entonces supongo que comenzaría con un diccionario de palabras indexadas por longitud. Entonces crearías otro diccionario de diccionarios de diccionarios ...el primer índice es por longitud total de palabra, mientras que el segundo es por longitud, luego por posición de letra, y finalmente por letra (palabras de seis letras con una segunda letra de "i", por ejemplo).

Después de que haya creado este índice, puede expresar cada paso al intentar establecer o resolver un rompecabezas en términos de las operaciones de conjunto realizadas en ellos. (Por ejemplo, una palabra de 8 letras que comienza con "w" y termina con "k" sería la intersección de las 8 palabras de letras que comienzan con "w" y todas las que terminan con "k" --- que, como era de esperar, incluirían "tarea" "). Después de haber construido la estructura de datos indexados que describí, por supuesto, permitir búsquedas mucho más eficientes para posibles coincidencias sería posible entonces realizando escaneos lineales de solo la lista de palabras global o incluso escaneos lineales de las listas de longitud separada).

Una vez que tenga esta estructura de datos básica, el resto del programa sería, presumiblemente, una generación de árbol y recorrido (con retroceso por supuesto). Cree un programa que genere todas las posibilidades (usando la estructura de datos descrita) y una vez que "se atasque" haga que retroceda hasta que encuentre nuevas posibilidades.

Según lo implicado por paxdiablo, deberá incluir un grupo grande de "palabras" para que el generador tenga una posibilidad razonable de crear una "solución" completa. Cualquiera que tenga experiencia con los crucigramas se da cuenta de que le permiten al colocador tomar bastantes libertades (como el uso frecuente de puntos de la brújula, términos arcaicos y contratos poéticos) con el fin de obtener la joroba por así decirlo.

No he escrito personalmente un generador de crucigramas. He escrito solucionadores de criptogramas que usaban una estructura de indexación similar, aunque mucho más simple. (Para encontrar cada palabra que zyzxw podría estar en un criptograma, "abstrae" en un patrón: abacd. Su diccionario contiene cada palabra indexada por su abstracción, y puede encontrar fácilmente que "cada" coincide con "zyzxw"). En ese caso, las búsquedas lineales a través de las listas iniciadas en cada abstracción son razonablemente rápidas, incluso cuando te estás correlacionando para descubrir que "uvz" con "zyzxw" podría, de hecho, ser "el" ... por ejemplo). También escribí un juego simple "Jotto" que no se beneficia de la indexación en absoluto --- escaneo lineal a través de las pocas miles de palabras de 5 o 6 letras en cada paso de eliminación utilizado para tomar mucho menos de un segundo en mi antiguo 6 Mhz XT en la prehistoria de la computación de PC moderna).

11

Puede que le interese buscar en Google el documento de investigación "The World's Fastest Scrabble Program" por Appel y Jacobson (1988). Los algoritmos están delineados en pseudocódigo, por lo que se necesita un poco de trabajo para moldearlo en forma utilizable y pegarlo todo; sin embargo, el programa que describen los autores funciona muy bien.

4

Steven A. Gordon escribió un interesante artículo sobre cómo buscar posibles movimientos de Scrabble (tm supongo) (ver Gordon's paper on GADDAG). Si bien existe una gran brecha entre la búsqueda de movimientos y ganar en Scrabble, como se menciona en el documento, esto no es relevante para la pregunta original.

En caso de que le resulte más útil simplemente ir directamente a leer algún código, hay un buen reproductor de código abierto, Quackle.

0

Busque el trabajo de tesis doctoral titulado "Hacia el juego perfecto del Scrabble" por Brian Sheppard (autor de Maven). Es informativo y muy interesante. Pero también muy largo.

1

La mayoría de los artículos de Scrabble hablan de buscar en toda la pizarra la mejor palabra para jugar. Pero para resolver su problema, como se dijo, hay un algoritmo bastante simple.

Primero, usted sabe que la palabra que desea contiene 'aplicación', y usted sabe que la palabra más grande que puede hacer es de siete letras (3 letras ya en la pizarra y 4 en la bandeja).Por lo tanto, buscar en su base de datos con una instrucción SQL tales como:

Seleccionar palabra del diccionario, donde palabras como '% aplicación%' y len (palabra) < = 7

A continuación, poner todas las siete cartas en una matriz { l, e, c, r, a, p, p}

Lea cada palabra de la base de datos, de a una por vez. Luego observe cada carácter de la palabra del diccionario y vea si existe en la matriz. Si la primera letra de la palabra del diccionario se encuentra en la matriz, elimine ese elemento en la matriz y pase a la siguiente letra del diccionario.

Si no se encuentra ninguna palabra de la palabra del diccionario en la matriz, entonces la palabra no califica, por lo tanto, pase a la siguiente palabra.

Si ha examinado todas las letras en el diccionario y todas han sido encontradas en la matriz, entonces la palabra califica, y entonces la escribe en la lista.

Nota: la razón por la que coloca sus mosaicos en una matriz es para que una vez que una letra de la palabra del diccionario coincida con una tesela de su matriz, deba eliminar esa letra de una consideración posterior, eliminando ese elemento en el conjunto.

Así, por ejemplo, la base de datos del diccionario devuelve la palabra 'apelación'. Las primeras cuatro letras se encuentran en su matriz, y esos elementos se eliminan, dejando solo {l, c, r} a la izquierda en la matriz. Cuando busca la quinta letra "a" no la encontrará, por lo que la palabra queda descalificada.

La palabra 'manzana' califica, dejando {c, r} a la izquierda en su conjunto.

Es bastante fácil codificar esto en cualquier idioma. Sin embargo, no es la forma más rápida de hacerlo. ¡Estoy buscando una manera más rápida yo mismo!

0

Si he entendido bien la pregunta (que comienzan las cartas de sugerencia w, una subcadena de la palabra, y se intenta reorganizar las letras para obtener una palabra correcta) aquí es otra solución:

Puede empezar por ir hacia atrás . Ya tiene las palabras en el diccionario y necesita mostrar parte de la palabra (una subcadena) y una lista de letras de la palabra para que la gente pueda organizarlas. Dado todo esto, empiezas con la palabra del diccionario y creas un gráfico de palabras con una distancia de edición de 1.

Ejemplo

de inicio con manzana y mantener la eliminación de una carta. Aquí es un pequeño gráfico (por lo que no me saco todos los bordes para reducir el desorden):

manzana -> appe -> mono -> ...
& nbsp \ & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp \
& nbsp & nbsp \ _ -> appl -> aplicación -> ...

Al quitar la carta, la coloca en una lista de sugerencias.

consejos: l, p

consejos: L, E

A medida que el jugador utiliza las letras de la lista para formar la palabra original, que sólo aceptan entradas correctas que son nodos que conducen a un padre anterior. Simplemente recorre el gráfico hacia atrás para encontrar la palabra original.

Ejemplo

Si la palabra es aplicación y consejos: l, p

Si el usuario le da l: Appl se mueve a un nodo prev de aplicación que es appl.

Si el usuario le da e: appe se mueve a un nodo anterior de la aplicación que es appe en este caso.

Cualquier otra letra que el usuario ingrese, no permite permanecer en el nodo actual.

-1

Lo que está buscando es la capacidad de su anagrama para encontrar letras "comodín" para ver qué palabras puede hacer con letras adicionales. Tengo un solucionador de anagramas que escribí que hace esto exactamente. Una cosa importante que descubrí para hacer esto, y también para la velocidad del solucionador es predefinir cuántas letras y el puntaje de cada palabra en su tabla de palabras.

Para la mesa Instancia usted debe estar estructurado como este

word | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z | score 
------------------------------------------------------------------------------------------------------------- 
test | 0 | 0 | 0 | 0 | 1 | 0 | 0 | h | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 4 

Como se puede ver que hay una columna separada para la palabra, la letra y el número de la carta que contienen y la puntuación de la palabra. Creé esto de antemano con un script separado que simplemente se ejecutaba para cada palabra y lo llenaba hasta que terminaba.

Aquí está el script que escribí para calcular cuántas letras había en cada palabra, así como el puntaje y actualicé cada registro. Debe comenzar con una tabla que solo contenga las palabras antes de poder ejecutar esta secuencia de comandos. Una vez que lo ejecuta, está listo y no tiene que volver a ejecutarlo a menos que agregue palabras nuevas.

<? 
include('/includes/connect.php'); 
$sql = "SELECT * FROM SOWPODS WHERE word LIKE 'z%' ORDER BY word ASC"; 
$result = mysql_query($sql); 
while($row = mysql_fetch_array($result)) { 
$string = $row['word']; 
$rowwordid = $row['ID']; 
echo $thisword = strtoupper($row['word']); 
echo " - "; 
for ($ii = 0; $ii < strlen($string); ++$ii) { 
    $thisletter = strtolower($string{$ii}); 
    if ($thisletter == 'a') { 
     $a = $a+1; 
    } elseif ($thisletter == 'b') { 
     $b = $b+1; 
    } elseif ($thisletter == 'c') { 
     $c = $c+1; 
    } elseif ($thisletter == 'd') { 
     $d = $d+1; 
    } elseif ($thisletter == 'e') { 
     $e = $e+1; 
    } elseif ($thisletter == 'f') { 
     $f = $f+1; 
    } elseif ($thisletter == 'g') { 
     $g = $g+1; 
    } elseif ($thisletter == 'h') { 
     $h = $h+1; 
    } elseif ($thisletter == 'i') { 
     $i = $i+1; 
    } elseif ($thisletter == 'j') { 
     $j = $j+1; 
    } elseif ($thisletter == 'k') { 
     $k = $k+1; 
    } elseif ($thisletter == 'l') { 
     $l = $l+1; 
    } elseif ($thisletter == 'm') { 
     $m = $m+1; 
    } elseif ($thisletter == 'n') { 
     $n = $n+1; 
    } elseif ($thisletter == 'o') { 
     $o = $o+1; 
    } elseif ($thisletter == 'p') { 
     $p = $p+1; 
    } elseif ($thisletter == 'q') { 
     $q = $q+1; 
    } elseif ($thisletter == 'r') { 
     $r = $r+1; 
    } elseif ($thisletter == 's') { 
     $s = $s+1; 
    } elseif ($thisletter == 't') { 
     $t = $t+1; 
    } elseif ($thisletter == 'u') { 
     $u = $u+1; 
    } elseif ($thisletter == 'v') { 
     $v = $v+1; 
    } elseif ($thisletter == 'w') { 
     $w = $w+1; 
    } elseif ($thisletter == 'x') { 
     $x = $x+1; 
    } elseif ($thisletter == 'y') { 
     $y = $y+1; 
    } elseif ($thisletter == 'z') { 
     $z = $z+1; 
    } 
} 
$scorea = $a*1; 
$scoreb = $b*4; 
$scorec = $c*4; 
$scored = $d*2; 
$scoree = $e*1; 
$scoref = $f*4; 
$scoreg = $g*3; 
$scoreh = $h*3; 
$scorei = $i*1; 
$scorej = $j*10; 
$scorek = $k*5; 
$scorel = $l*2; 
$scorem = $m*4; 
$scoren = $n*2; 
$scoreo = $o*1; 
$scorep = $p*4; 
$scoreq = $q*10; 
$scorer = $r*1; 
$scores = $s*1; 
$scoret = $t*1; 
$scoreu = $u*2; 
$scorev = $v*5; 
$scorew = $w*4; 
$scorex = $x*8; 
$scorey = $y*3; 
$scorez = $z*10; 

$totalscore = $scorea + $scoreb + $scorec + $scored + $scoree + $scoref + $scoreg +  $scoreh + $scorei + $scorej + $scorek + $scorel + $scorem + $scoren + $scoreo + $scorep +  $scoreq + $scorer + $scores + $scoret + $scoreu + $scorev + $scorew + $scorex + $scorey + $scorez; 
$SQL_update_count = "UPDATE TWL06 SET a = '$a', b = '$b', c = '$c', d = '$d', e = '$e', f = '$f', g = '$g', h = '$h', i = '$i', j = '$j', k = '$k', l = '$l', m = '$m', n= '$n', o = '$o', p = '$p', q = '$q', r = '$r', s = '$s', t = '$t', u = '$u', v = '$v', w = '$w', x = '$x', y = '$y', z = '$z', score = '$totalscore' WHERE ID = '$rowwordid'"; 
echo "<br>"; 
$result_update_count = mysql_query($SQL_update_count); 

$a = 0; 
$b = 0; 
$c = 0; 
$d = 0; 
$e = 0; 
$f = 0; 
$g = 0; 
$h = 0; 
$i = 0; 
$j = 0; 
$k = 0; 
$l = 0; 
$m = 0; 
$n = 0; 
$o = 0; 
$p = 0; 
$q = 0; 
$r = 0; 
$s = 0; 
$t = 0; 
$u = 0; 
$v = 0; 
$w = 0; 
$x = 0; 
$y = 0; 
$z = 0; 
} 
?> 

Una vez hecho todo lo que tiene que hacer es hacer un script que cuenta las letras en las columnas y coincide con las cartas que le des. Tendrás que explotar las letras primero y descubrir cuántas de cada letra tienes. Luego ejecute una instrucción sql que encuentre esas cantidades de letra o menos.

Cuestiones relacionadas