Últimamente he estado jugando un juego en mi iPhone llamado Scramble. Algunos de ustedes pueden conocer este juego como Boggle. Esencialmente, cuando el juego comienza, obtienes una matriz de letras como esta:
F X I E
A M L O
E W B X
A S T U
El objetivo del juego es encontrar tantas palabras como sea posible que se puedan formar encadenando letras juntas. Puede comenzar con cualquier letra, y todas las letras que la rodean son juego limpio, y luego, una vez que pasa a la siguiente letra, todas las letras que rodean esa letra son juego limpio, excepto las letras utilizadas anteriormente . Así que en la rejilla superior, por ejemplo, podría llegar a las palabras LOB
, TUX
, SEA
, FAME
, etc. Las palabras deben tener al menos 3 caracteres y no más de caracteres NxN, lo que sería 16 en este juego, pero puede variar en algunas implementaciones . Si bien este juego es divertido y adictivo, aparentemente no soy muy bueno y quería hacer un poco de trampa haciendo un programa que me diera las mejores palabras posibles (cuanto más larga sea la palabra, más puntos obtendrás).
(fuente: boggled.org )
Desafortunadamente, no soy muy bueno con los algoritmos o sus eficiencias, etc. Mi primer intento utiliza un diccionario como este (~ 2.3MB) y realiza una búsqueda lineal tratando de hacer coincidir las combinaciones con las entradas del diccionario. Esto toma un muy largo tiempo para encontrar las palabras posibles, y ya que usted consigue solamente 2 minutos por ronda, simplemente no es suficiente.
Estoy interesado en ver si cualquier Stackoverflowers puede llegar a soluciones más eficientes. Principalmente estoy buscando soluciones usando Big 3 Ps: Python, PHP y Perl, aunque cualquier cosa con Java o C ++ también es genial, ya que la velocidad es esencial.
SOLUCIONES ACTUALES :
- Adam Rosenfield, Python, ~ 20 años
- John Fouhy, Python, ~ 3s
- Kent Fredric, Perl, ~ 1s
- Darius Bacon, Python, ~ 1s
- rvarcher, VB.NET (enlace en vivo) , ~ 1s
- Paolo Bergantino, PHP (enlace en vivo) , ~ 5s (~ 2s localmente)
Respuestas:
Mi respuesta funciona como las otras aquí, pero la publicaré porque se ve un poco más rápido que las otras soluciones de Python, desde la configuración del diccionario más rápido. (Verifiqué esto con la solución de John Fouhy). Después de la configuración, el tiempo para resolver es bajo en el ruido.
Uso de la muestra:
Editar: Filtre palabras de menos de 3 letras.
Edición 2: Tenía curiosidad por qué la solución Perl de Kent Fredric era más rápida; Resulta utilizar una coincidencia de expresiones regulares en lugar de un conjunto de caracteres. Hacer lo mismo en Python sobre duplica la velocidad.
fuente
def neighbors((x, y))
(sin sentido, por lo que puedo ver). También requiere paréntesis alrededor del argumento paraprint
.La solución más rápida que obtendrá probablemente implicará almacenar su diccionario en un archivo . Luego, cree una cola de trillizos ( x , y , s ), donde cada elemento en la cola corresponde a un prefijo s de una palabra que se puede deletrear en la cuadrícula, que termina en la ubicación ( x , y ). Inicialice la cola con N x N elementos (donde N es el tamaño de su cuadrícula), un elemento para cada cuadrado en la cuadrícula. Luego, el algoritmo procede de la siguiente manera:
Si almacena su diccionario en un trie, puede comprobar si s + c es una palabra o un prefijo de una palabra en tiempo constante (siempre que también mantenga algunos metadatos adicionales en cada dato de cola, como un puntero al nodo actual en el trie), entonces el tiempo de ejecución de este algoritmo es O (número de palabras que se pueden deletrear).
[Editar] Aquí hay una implementación en Python que acabo de codificar:
Ejemplo de uso:
Salida:
Notas: Este programa no genera palabras de 1 letra ni filtra por longitud de palabra. Eso es fácil de agregar pero no es realmente relevante para el problema. También genera algunas palabras varias veces si se pueden deletrear de varias maneras. Si una palabra dada se puede deletrear de muchas maneras diferentes (el peor de los casos: cada letra en la cuadrícula es la misma (por ejemplo, 'A') y una palabra como 'aaaaaaaaaa' está en su diccionario), entonces el tiempo de ejecución será terriblemente exponencial . Filtrar duplicados y ordenar es trivial debido después de que el algoritmo haya terminado.
fuente
(x,y)
, un posible seguidor es(x+1,y+1)
. Luego(x+1,y+1)
es empujado a la cola. Sin embargo(x,y)
, también será un seguidor(x+1,y+1)
, ¿entonces eso no conducirá a un rebote interminable entre ellos?Para acelerar el diccionario, hay una transformación / proceso general que puede hacer para reducir en gran medida las comparaciones del diccionario con anticipación.
Dado que la cuadrícula anterior contiene solo 16 caracteres, algunos de ellos duplicados, puede reducir en gran medida el número total de claves en su diccionario simplemente filtrando las entradas que tienen caracteres inalcanzables.
Pensé que esta era la optimización obvia, pero al ver que nadie lo hizo, lo estoy mencionando.
Me redujo de un diccionario de 200,000 claves a solo 2,000 claves simplemente durante el pase de entrada. Esto al menos reduce la sobrecarga de memoria, y eso seguramente se asignará a un aumento de velocidad en algún lugar ya que la memoria no es infinitamente rápida.
Implementación de Perl
Mi implementación es un poco pesada porque le di importancia a poder conocer la ruta exacta de cada cadena extraída, no solo la validez de la misma.
También tengo algunas adaptaciones allí que teóricamente permitirán que funcione una cuadrícula con agujeros y cuadrículas con líneas de diferentes tamaños (suponiendo que obtenga la entrada correcta y se alinee de alguna manera).
El filtro inicial es, con mucho, el cuello de botella más significativo en mi aplicación, como se sospechaba anteriormente, al comentar que la línea lo hincha de 1.5 sa 7.5.
Tras la ejecución, parece pensar que todos los dígitos individuales están en sus propias palabras válidas, pero estoy bastante seguro de que eso se debe a cómo funciona el archivo del diccionario.
Está un poco hinchado, pero al menos reutilizo Tree :: Trie de cpan
Algunos de ellos se inspiraron parcialmente en las implementaciones existentes, algunos de ellos ya los tenía en mente.
Críticas constructivas y formas en que podría mejorarse bienvenido (/ me señala que nunca buscó en CPAN un solucionador de problemas , pero fue más divertido resolverlo)
actualizado para nuevos criterios
Información de arco / ejecución para comparación:
Más murmullos sobre esa optimización de expresiones regulares
La optimización de expresiones regulares que uso es inútil para los diccionarios de resolución múltiple, y para la resolución múltiple, querrá un diccionario completo, no uno previamente recortado.
Sin embargo, dicho eso, para soluciones únicas, es realmente rápido. (Perl regex están en C! :))
Aquí hay algunas adiciones de código diferentes:
ps: 8.16 * 200 = 27 minutos.
fuente
Podrías dividir el problema en dos partes:
Idealmente, (2) también debe incluir una forma de probar si una cadena es un prefijo de una palabra válida; esto le permitirá podar su búsqueda y ahorrar un montón de tiempo.
La Trie de Adam Rosenfield es una solución para (2). Es elegante y probablemente lo que prefiera su especialista en algoritmos, pero con lenguajes modernos y computadoras modernas, podemos ser un poco más vagos. Además, como sugiere Kent, podemos reducir el tamaño de nuestro diccionario descartando palabras que no tengan letras en la cuadrícula. Aquí hay algo de python:
Guau; prueba de prefijo de tiempo constante. Tarda un par de segundos en cargar el diccionario que vinculó, pero solo un par :-) (tenga en cuenta que
words <= prefixes
)Ahora, para la parte (1), me inclino a pensar en términos de gráficos. Así que construiré un diccionario que se vea así:
graph[(x, y)]
es decir, es el conjunto de coordenadas que puede alcanzar desde su posición(x, y)
. También agregaré un nodo ficticioNone
que se conectará a todo.Construir es un poco torpe, porque hay 8 posiciones posibles y tienes que verificar los límites. Aquí hay un código de python correspondientemente torpe:
Este código también crea una asignación de diccionario
(x,y)
al carácter correspondiente. Esto me permite convertir una lista de posiciones en una palabra:Finalmente, hacemos una búsqueda profunda primero. El procedimiento básico es:
Pitón:
Ejecute el código como:
e inspeccionar
res
para ver las respuestas. Aquí hay una lista de palabras encontradas para su ejemplo, ordenadas por tamaño:El código tarda (literalmente) un par de segundos en cargar el diccionario, pero el resto es instantáneo en mi máquina.
fuente
range(len(w)+1)
lugar derange(len(w))
. Afirmé eso,words <= prefixes
pero aparentemente no probé eso: - /Mi intento en Java. Tarda unos 2 s en leer el archivo y construir trie, y alrededor de 50 ms para resolver el rompecabezas. Utilicé el diccionario vinculado en la pregunta (tiene algunas palabras que no sabía que existían en inglés, como fae, ima)
El código fuente consta de 6 clases. Los publicaré a continuación (si esta no es la práctica correcta en StackOverflow, dígame).
gineer.bogglesolver.Main
gineer.bogglesolver.Solver
gineer.bogglesolver.trie.Node
gineer.bogglesolver.trie.Trie
gineer.bogglesolver.util.Constants
gineer.bogglesolver.util.Util
fuente
Creo que probablemente pasarás la mayor parte de tu tiempo tratando de unir palabras que posiblemente no se pueden construir con tu cuadrícula de letras. Entonces, lo primero que haría es tratar de acelerar ese paso y eso debería llevarlo a la mayor parte del camino.
Para esto, volvería a expresar la cuadrícula como una tabla de posibles "movimientos" que indices según la transición de letras que estás viendo.
Comience asignando a cada letra un número de su alfabeto completo (A = 0, B = 1, C = 2, ... y así sucesivamente).
Tomemos este ejemplo:
Y por ahora, usemos el alfabeto de las letras que tenemos (por lo general, probablemente quieras usar el mismo alfabeto completo cada vez):
Luego crea una matriz booleana 2D que le indica si tiene disponible una determinada transición de letras:
Ahora revisa tu lista de palabras y convierte las palabras en transiciones:
Luego verifique si estas transiciones están permitidas buscándolas en su tabla:
Si todos están permitidos, existe la posibilidad de que se encuentre esta palabra.
Por ejemplo, la palabra "casco" se puede descartar en la cuarta transición (m to e: helMEt), ya que esa entrada en su tabla es falsa.
Y la palabra hámster se puede descartar, ya que la primera transición (h a a) no está permitida (ni siquiera existe en su tabla).
Ahora, para las pocas palabras restantes que probablemente no eliminó, intente encontrarlas realmente en la cuadrícula de la forma en que lo hace ahora o como se sugiere en algunas de las otras respuestas aquí. Esto es para evitar falsos positivos que resultan de saltos entre letras idénticas en su cuadrícula. Por ejemplo, la palabra "ayuda" está permitida por la tabla, pero no por la cuadrícula.
Algunos consejos adicionales para mejorar el rendimiento de esta idea:
En lugar de usar una matriz 2D, use una matriz 1D y simplemente calcule el índice de la segunda letra usted mismo. Entonces, en lugar de una matriz de 12x12 como la anterior, haga una matriz 1D de longitud 144. Si usa siempre el mismo alfabeto (es decir, una matriz 26x26 = 676x1 para el alfabeto inglés estándar), incluso si no aparecen todas las letras en su cuadrícula , puede calcular previamente los índices en esta matriz 1D que necesita probar para que coincidan con las palabras del diccionario. Por ejemplo, los índices para 'hola' en el ejemplo anterior serían
Extienda la idea a una tabla 3D (expresada como una matriz 1D), es decir, todas las combinaciones de 3 letras permitidas. De esa manera, puede eliminar aún más palabras de inmediato y reducir el número de búsquedas de matriz para cada palabra en 1: para 'hola', solo necesita 3 búsquedas de matriz: hel, ell, llo. Por cierto, será muy rápido construir esta tabla, ya que solo hay 400 movimientos posibles de 3 letras en su cuadrícula.
Precalcule los índices de los movimientos en su cuadrícula que necesita incluir en su tabla. Para el ejemplo anterior, debe establecer las siguientes entradas en 'Verdadero':
Estoy seguro de que si usa este enfoque, puede hacer que su código se ejecute increíblemente rápido, si tiene el diccionario precalculado y ya cargado en la memoria.
Por cierto: Otra buena cosa que hacer, si estás creando un juego, es ejecutar este tipo de cosas inmediatamente en segundo plano. Comience a generar y resolver el primer juego mientras el usuario sigue mirando la pantalla de título de su aplicación y coloca el dedo en posición para presionar "Reproducir". Luego genera y resuelve el siguiente juego mientras el usuario juega el anterior. Eso debería darle mucho tiempo para ejecutar su código.
(Me gusta este problema, así que probablemente estaré tentado de implementar mi propuesta en Java en algún momento en los próximos días para ver cómo funcionaría realmente ... Publicaré el código aquí una vez que lo haga).
ACTUALIZAR:
Ok, tuve algo de tiempo hoy e implementé esta idea en Java:
Aquí hay algunos resultados:
Para la cuadrícula de la imagen publicada en la pregunta original (DGHI ...):
Para las cartas publicadas como ejemplo en la pregunta original (FXIE ...)
Para la siguiente cuadrícula de 5x5:
da esto:
Para esto utilicé la Lista de palabras de Scrabble del torneo TWL06 , ya que el enlace en la pregunta original ya no funciona. Este archivo tiene 1,85 MB, por lo que es un poco más corto. Y la
buildDictionary
función arroja todas las palabras con menos de 3 letras.Aquí hay un par de observaciones sobre el desempeño de esto:
Es aproximadamente 10 veces más lento que el rendimiento reportado de la implementación OCaml de Victor Nicollet. Si esto es causado por el algoritmo diferente, el diccionario más corto que utilizó, el hecho de que su código está compilado y el mío se ejecuta en una máquina virtual Java, o el rendimiento de nuestras computadoras (el mío es un Intel Q6600 @ 2.4MHz con WinXP), No lo sé. Pero es mucho más rápido que los resultados para las otras implementaciones citadas al final de la pregunta original. Entonces, si este algoritmo es superior al diccionario trie o no, no lo sé en este momento.
El método de tabla utilizado en
checkWordTriplets()
produce una muy buena aproximación a las respuestas reales. Solo 1 de cada 3-5 palabras aprobadas no pasará lacheckWords()
prueba (ver el número de candidatos versus el número de palabras reales arriba).Algo que no puede ver arriba: la
checkWordTriplets()
función tarda unos 3,65 ms y, por lo tanto, es totalmente dominante en el proceso de búsqueda. LacheckWords()
función ocupa más o menos los 0.05-0.20 ms restantes.¡El tiempo de ejecución de la
checkWordTriplets()
función depende linealmente del tamaño del diccionario y es prácticamente independiente del tamaño del tablero!El tiempo de ejecución de
checkWords()
depende del tamaño del tablero y del número de palabras que no se descartancheckWordTriplets()
.La
checkWords()
implementación anterior es la primera versión más tonta que se me ocurrió. Básicamente no está optimizado en absoluto. Pero en comparación concheckWordTriplets()
esto es irrelevante para el rendimiento total de la aplicación, así que no me preocupé por eso. Pero , si el tamaño de la placa aumenta, esta función se volverá cada vez más lenta y eventualmente comenzará a tener importancia. Entonces, también necesitaría ser optimizado.Una cosa buena de este código es su flexibilidad:
initializeBoard()
.Ok, pero creo que esta publicación ya es muuuuucho tiempo suficiente. Definitivamente puedo responder cualquier pregunta que pueda tener, pero pasemos a los comentarios.
fuente
ok = possibleTriplets[entry.triplets[t]];
. hmm?entry.letters[i] = (byte) letter - 65;
simplemente toma el valor ASCII y resta 65 ("A"). Si tiene Umlauts o letras minúsculas en su diccionario, esto dará valores superiores a 31, que no están planificados por la configuración del tamaño del alfabeto en la línea 9. Para admitir otras letras, tendría que expandir esta línea para mapearlos en el rango permitido por el tamaño del alfabeto.Sorprendentemente, nadie intentó una versión PHP de esto.
Esta es una versión PHP en funcionamiento de la solución Python de John Fouhy.
Aunque tomé algunos consejos de las respuestas de todos los demás, esto se copió principalmente de John.
Aquí hay un enlace en vivo si quieres probarlo. Aunque toma ~ 2s en mi máquina local, toma ~ 5s en mi servidor web. En cualquier caso, no es muy rápido. Aún así, es bastante horrible, así que puedo imaginar que el tiempo se puede reducir significativamente. Cualquier sugerencia sobre cómo lograr eso sería apreciada. La falta de tuplas de PHP hizo que las coordenadas fueran raras para trabajar y mi incapacidad para comprender qué demonios está pasando no ayudó en absoluto.
EDITAR : algunas correcciones hacen que tome menos de 1s localmente.
fuente
¿No te interesa VB? :) No pude resistirme. He resuelto esto de manera diferente a muchas de las soluciones presentadas aquí.
Mis tiempos son:
EDITAR: Los tiempos de carga del diccionario en el servidor de alojamiento web se ejecutan aproximadamente de 1 a 1.5 segundos más que la computadora de mi casa.
No sé qué tan mal se deteriorarán los tiempos con una carga en el servidor.
Escribí mi solución como una página web en .Net. myvrad.com/boggle
Estoy usando el diccionario al que se hace referencia en la pregunta original.
Las letras no se reutilizan en una palabra. Solo se encuentran palabras de 3 caracteres o más.
Estoy usando una tabla hash de todos los prefijos y palabras únicas en lugar de un trie. No sabía nada de Trie, así que aprendí algo allí. La idea de crear una lista de prefijos de palabras además de las palabras completas es lo que finalmente redujo mis tiempos a un número respetable.
Lea los comentarios del código para obtener detalles adicionales.
Aquí está el código:
fuente
Tan pronto como vi la declaración del problema, pensé "Trie". Pero al ver que varios otros carteles hicieron uso de ese enfoque, busqué otro enfoque para ser diferente. Por desgracia, el enfoque Trie funciona mejor. Ejecuté la solución Perl de Kent en mi máquina y tardé 0,31 segundos en ejecutarse, después de adaptarla para usar mi archivo de diccionario. Mi propia implementación de Perl requirió 0,54 segundos para ejecutarse.
Este fue mi enfoque:
Cree un hash de transición para modelar las transiciones legales.
Repite las 16 ^ 3 combinaciones posibles de tres letras.
Luego recorra todas las palabras en el diccionario.
Imprime las palabras que encontré.
Intenté secuencias de 3 letras y 4 letras, pero las secuencias de 4 letras ralentizaron el programa.
En mi código, uso / usr / share / dict / words para mi diccionario. Viene de serie en MAC OS X y muchos sistemas Unix. Puede usar otro archivo si lo desea. Para resolver un rompecabezas diferente, simplemente cambie la variable @puzzle. Esto sería fácil de adaptar para matrices más grandes. Solo necesitaría cambiar el hash% transitions y el hash% legalTransitions.
La fortaleza de esta solución es que el código es corto y las estructuras de datos simples.
Aquí está el código Perl (que usa demasiadas variables globales, lo sé):
fuente
Sé que llegué muy tarde, pero hice uno de estos hace un tiempo en PHP , solo por diversión ...
http://www.lostsockdesign.com.au/sandbox/boggle/index.php?letters=fxieamloewbxastu Se encontraron 75 palabras (133 pts) en 0.90108 segundos
F.........X..I..............E............... A......................................M..............................L............................O............................... E....................W............................B..........................X A..................S..................................................T.................U....
Da alguna indicación de lo que el programa está haciendo realmente: cada letra es donde comienza a mirar a través de los patrones mientras que cada ''. muestra un camino que ha intentado tomar. El más '.' hay más lejos que ha buscado.
Avíseme si desea el código ... es una horrible combinación de PHP y HTML que nunca tuvo la intención de ver la luz del día, así que no me atrevo a publicarlo aquí: P
fuente
Pasé 3 meses trabajando en una solución para el problema de las 10 mejores tablas de Boggle densas de 5 puntos.
El problema ahora está resuelto y presentado con divulgación completa en 5 páginas web. Por favor, contácteme con preguntas.
El algoritmo de análisis de tablero utiliza una pila explícita para atravesar pseudo-recursivamente los cuadrados del tablero a través de un gráfico de palabra acíclico dirigido con información directa del niño y un mecanismo de seguimiento de sello de tiempo. Esta puede ser la estructura de datos de léxico más avanzada del mundo.
El esquema evalúa unos 10,000 tableros muy buenos por segundo en un núcleo cuádruple. (9500+ puntos)
Página web principal:
DeepSearch.c - http://www.pathcom.com/~vadco/deep.html
Páginas web de componentes:
Cuadro de indicadores óptimo: http://www.pathcom.com/~vadco/binary.html
Estructura avanzada del léxico: http://www.pathcom.com/~vadco/adtdawg.html
Algoritmo de análisis de la placa: http://www.pathcom.com/~vadco/guns.html
Procesamiento de lotes paralelos: http://www.pathcom.com/~vadco/parallel.html
- Este conjunto integral de trabajo solo interesará a una persona que exige lo mejor.
fuente
¿Su algoritmo de búsqueda disminuye continuamente la lista de palabras a medida que continúa su búsqueda?
Por ejemplo, en la búsqueda anterior solo hay 13 letras con las que sus palabras pueden comenzar (reduciendo efectivamente a la mitad la cantidad de letras iniciales).
A medida que agrega más permutaciones de letras, disminuiría aún más los conjuntos de palabras disponibles, disminuyendo la búsqueda necesaria.
Yo comenzaría por allí.
fuente
Tendría que pensar más en una solución completa, pero como una práctica optimización, me pregunto si valdría la pena calcular previamente una tabla de frecuencias de digramas y trigramas (combinaciones de 2 y 3 letras) basadas en todas las palabras de su diccionario, y use esto para priorizar su búsqueda. Iría con las letras iniciales de las palabras. Entonces, si su diccionario contenía las palabras "India", "Agua", "Extremo" y "Extraordinario", entonces su tabla calculada previamente podría ser:
Luego busque estos digramas en el orden de comunidad (primero EX, luego WA / IN)
fuente
Primero, lea cómo uno de los diseñadores del lenguaje C # resolvió un problema relacionado: http://blogs.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx .
Al igual que él, puede comenzar con un diccionario y canonizar las palabras creando un diccionario a partir de una serie de letras ordenadas alfabéticamente a una lista de palabras que se pueden deletrear a partir de esas letras.
Luego, comience a crear las posibles palabras del tablero y búsquelas. Sospecho que eso te llevará bastante lejos, pero ciertamente hay más trucos que podrían acelerar las cosas.
fuente
Sugiero hacer un árbol de letras basado en palabras. El árbol estaría compuesto de estructuras de letras, como esta:
Luego construyes el árbol, con cada profundidad agregando una nueva letra. En otras palabras, en el primer nivel estaría el alfabeto; luego, de cada uno de esos árboles, habría otras 26 entradas más, y así sucesivamente, hasta que haya deletreado todas las palabras. Cuelgue este árbol analizado y hará que todas las respuestas posibles sean más rápidas de buscar.
Con este árbol analizado, puede encontrar soluciones muy rápidamente. Aquí está el pseudocódigo:
Esto podría acelerarse con un poco de programación dinámica. Por ejemplo, en su muestra, las dos 'A' están al lado de una 'E' y una 'W', que (desde el punto en que las golpearon) serían idénticas. No tengo tiempo suficiente para deletrear realmente el código para esto, pero creo que puedes entender la idea.
Además, estoy seguro de que encontrará otras soluciones si busca "solucionador de Boggle" en Google.
fuente
Solo por diversión, implementé uno en bash. No es súper rápido, pero sí razonable.
http://dev.xkyle.com/bashboggle/
fuente
Divertidísimo. ¡Casi publiqué la misma pregunta hace unos días debido al mismo maldito juego! Sin embargo, no lo hice porque solo busqué en google el boggle solver python y obtuve todas las respuestas que podía desear.
fuente
Me doy cuenta de que el momento de esta pregunta llegó y se fue, pero como estaba trabajando en un solucionador y me topé con esto mientras buscaba en Google, pensé que debería publicar una referencia a la mía, ya que parece un poco diferente de algunos de los otros.
Elegí ir con una matriz plana para el tablero de juego, y hacer búsquedas recursivas de cada letra en el tablero, atravesando de vecino válido a vecino válido, extendiendo la búsqueda si la lista actual de letras es un prefijo válido en un índice. Al atravesar la noción de la palabra actual hay una lista de índices en el tablero, no letras que forman una palabra. Al verificar el índice, los índices se traducen a letras y se realiza la verificación.
El índice es un diccionario de fuerza bruta que es un poco como un trie, pero permite consultas Pythonic del índice. Si las palabras 'gato' y 'atender' están en la lista, obtendrá esto en el diccionario:
Entonces, si current_word es 'ca', sabe que es un prefijo válido porque
'ca' in d
devuelve True (así que continúe el recorrido del tablero). Y si current_word es 'cat', entonces sabe que es una palabra válida porque es un prefijo válido y también'cat' in d['cat']
devuelve True.Si se siente así, esto permitió un código legible que no parece demasiado lento. Como todos los demás, el gasto en este sistema es leer / construir el índice. Resolver el tablero es bastante ruido.
El código está en http://gist.github.com/268079 . Es intencionalmente vertical e ingenuo con muchas comprobaciones de validez explícitas porque quería comprender el problema sin desmoronarlo con un montón de magia u oscuridad.
fuente
Escribí mi solucionador en C ++. Implementé una estructura de árbol personalizada. No estoy seguro de que pueda considerarse un trie pero es similar. Cada nodo tiene 26 ramas, 1 para cada letra del alfabeto. Atravesé las ramas del tablero de boggle en paralelo con las ramas de mi diccionario. Si la rama no existe en el diccionario, dejo de buscarla en el tablero de Boggle. Convierto todas las letras en el tablero a ints. Entonces 'A' = 0. Como son solo matrices, la búsqueda siempre es O (1). Cada nodo almacena si completa una palabra y cuántas palabras existen en sus elementos secundarios. El árbol se poda a medida que se encuentran las palabras para reducir la búsqueda repetida de las mismas palabras. Creo que la poda también es O (1).
CPU: Pentium SU2700 1.3GHz
RAM: 3gb
Carga el diccionario de 178,590 palabras en <1 segundo.
Resuelve Boggle 100x100 (boggle.txt) en 4 segundos. ~ 44,000 palabras encontradas.
Resolver un Boggle 4x4 es demasiado rápido para proporcionar un punto de referencia significativo. :)
Rápido Boggle Solver GitHub Repo
fuente
Dado un tablero Boggle con N filas y columnas M, supongamos lo siguiente:
Bajo estos supuestos, la complejidad de esta solución es O (N * M).
Creo que comparar los tiempos de ejecución de esta placa de ejemplo de muchas maneras no lo comprende, pero, en aras de la exhaustividad, esta solución se completa en <0.2s en mi MacBook Pro moderna.
Esta solución encontrará todas las rutas posibles para cada palabra en el corpus.
fuente
Esta solución también da la dirección para buscar en el tablero dado
Algo:
Salida:
Código:
fuente
He implementado una solución en OCaml . Precompila un diccionario como un trie y utiliza frecuencias de secuencia de dos letras para eliminar los bordes que nunca podrían aparecer en una palabra para acelerar aún más el procesamiento.
Resuelve su placa de ejemplo en 0,35 ms (con un tiempo de inicio adicional de 6 ms que está relacionado principalmente con la carga del trie en la memoria).
Las soluciones encontradas:
fuente
/usr/share/dict
diccionario local , y algunas de las palabras faltan (como EMBOLE).Una solución JavaScript Node.JS. Calcula las 100 palabras únicas en menos de un segundo, lo que incluye leer el archivo del diccionario (MBA 2012).
Salida:
["FAM", "TUX", "TUB", "FAE", "ELI", "ELM", "ELB", "TWA", "TWA", "SAW", "AMI", "SWA" , "SWA", "AME", "SEA", "SEW", "AES", "AWL", "AWE", "SEA", "AWA", "MIX", "MIL", "AST", " ASE "," MAX "," MAE "," MAW "," MEW "," AWE "," MES "," AWL "," LIE "," LIM "," AWA "," AES "," PERO " , "BLO", "WAS", "WAE", "WEA", "LEI", "LEO", "LOB", "LOX", "WEM", "OIL", "OLM", "WEA", " WAE "," WAX "," WAF ","MILO", "ESTE", "WAME", "TWAS", "TWAE", "EMIL", "WEAM", "OIME", "AXIL", "WEST", "TWAE", "LIMB", "WASE "," WAST "," BLEO "," STUB "," BOIL "," BOLE "," LIME "," SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME ", "ASEM", "MILE", "AMIL", "SEAX", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "AWEST" "," AWEST "," LIMAX "," LIMES "," LIMBU "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]ESTE "," WAME "," TWAS "," TWAE "," EMIL "," WEAM "," OIME "," AXIL "," WEST "," TWAE "," LIMB "," WASE "," WAST " , "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA", "MEWL", "AXLE", "FAME", "ASEM", " MILLA "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI "," AXILE "," AMBLE "," SWAMI "," AWEST "," AWEST " , "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE", "FAMBLE"]ESTE "," WAME "," TWAS "," TWAE "," EMIL "," WEAM "," OIME "," AXIL "," WEST "," TWAE "," LIMB "," WASE "," WAST " , "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA", "MEWL", "AXLE", "FAME", "ASEM", " MILLA "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI "," AXILE "," AMBLE "," SWAMI "," AWEST "," AWEST " , "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE", "FAMBLE"]"TWAE", "EMIL", "WEAM", "OIME", "AXIL", "WEST", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL" "," BOLE "," LIME "," SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX ", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU" "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]"TWAE", "EMIL", "WEAM", "OIME", "AXIL", "WEST", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL" "," BOLE "," LIME "," SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX ", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU" "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]"WEST", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA" "," MEWL "," EJE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI ", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE "," FAMBLE "]"WEST", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA" "," MEWL "," EJE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI ", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE "," FAMBLE "]SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM " , "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", " SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM " , "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", " SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]LIMAX "," LIMES "," LIMBU "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]LIMAX "," LIMES "," LIMBU "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]
Código:
fuente
Así que quería agregar otra forma de PHP para resolver esto, ya que a todos les encanta PHP. Hay un poco de refactorización que me gustaría hacer, como usar una coincidencia de expresión regular contra el archivo de diccionario, pero en este momento solo estoy cargando todo el archivo de diccionario en una lista de palabras.
Hice esto usando una idea de lista vinculada. Cada nodo tiene un valor de carácter, un valor de ubicación y un siguiente puntero.
El valor de ubicación es cómo descubrí si dos nodos están conectados.
Entonces, usando esa cuadrícula, sé que dos nodos están conectados si la ubicación del primer nodo es igual a la ubicación del segundo nodo +/- 1 para la misma fila, +/- 9, 10, 11 para la fila de arriba y abajo.
Yo uso la recursividad para la búsqueda principal. Quita una palabra de la lista de palabras, encuentra todos los puntos de inicio posibles y luego encuentra recursivamente la siguiente conexión posible, teniendo en cuenta que no puede ir a una ubicación que ya está usando (por eso agrego $ notInLoc).
De todos modos, sé que necesita un poco de refactorización, y me encantaría escuchar ideas sobre cómo hacerlo más limpio, pero produce los resultados correctos basados en el archivo de diccionario que estoy usando. Dependiendo de la cantidad de vocales y combinaciones en el tablero, toma alrededor de 3 a 6 segundos. Sé que una vez que haga coincidir los resultados del diccionario, eso se reducirá significativamente.
fuente
Sé que llego tarde a la fiesta, pero he implementado, como ejercicio de codificación, un solucionador de problemas en varios lenguajes de programación (C ++, Java, Go, C #, Python, Ruby, JavaScript, Julia, Lua, PHP, Perl) y Pensé que alguien podría estar interesado en eso, así que dejo el enlace aquí: https://github.com/AmokHuginnsson/boggle-solvers
fuente
Aquí está la solución Uso de palabras predefinidas en el kit de herramientas NLTK NLTK tiene el paquete nltk.corpus en el que tenemos un paquete llamado palabras y contiene más de 2 palabras de inglés Laakhs que simplemente puede usar todas en su programa.
Una vez que cree su matriz, conviértala en una matriz de caracteres y realice este código
Salida:
Espero que lo obtengas.
fuente
Aquí está mi implementación de Java: https://github.com/zouzhile/interview/blob/master/src/com/interview/algorithms/tree/BoggleSolver.java
La construcción de Trie tomó 0 horas, 0 minutos, 1 segundos, 532 milisegundos
La búsqueda de palabras tomó 0 horas, 0 minutos, 0 segundos, 92 milisegundos
Nota: Usé el diccionario y la matriz de caracteres al comienzo de este hilo. El código se ejecutó en mi MacBookPro, a continuación hay información sobre la máquina.
Nombre del modelo: MacBook Pro
Identificador del modelo: MacBookPro8,1
Nombre del procesador: Intel Core i5
Velocidad del procesador: 2.3 GHz
Número de procesadores: 1
Número total de núcleos: 2
Caché L2 (por núcleo): 256 KB
Caché L3: 3 MB
Memoria: 4
Versión ROM de arranque GB : MBP81.0047.B0E
Versión SMC (sistema): 1.68f96
fuente
También resolví esto con Java. Mi implementación tiene 269 líneas de largo y es bastante fácil de usar. Primero debe crear una nueva instancia de la clase Boggler y luego llamar a la función de resolución con la cuadrícula como parámetro. Tarda unos 100 ms en cargar el diccionario de 50 000 palabras en mi computadora y encuentra las palabras en unos 10-20 ms. Las palabras encontradas se almacenan en un ArrayList,
foundWords
.fuente
Resolví esto en c. Se tarda alrededor de 48 ms en ejecutarse en mi máquina (con alrededor del 98% del tiempo dedicado a cargar el diccionario desde el disco y crear el trie). El diccionario es / usr / share / dict / american-english que tiene 62886 palabras.
Código fuente
fuente
Resolví esto perfectamente y muy rápido. Lo puse en una aplicación de Android. Vea el video en el enlace de Play Store para verlo en acción.
Word Cheats es una aplicación que "descifra" cualquier juego de palabras estilo matriz. Esta aplicación fue creada para ayudarme a hacer trampa en word scrambler. ¡Se puede utilizar para búsquedas de palabras, ruzzle, palabras, buscador de palabras, crack de palabras, boggle y más!
Se puede ver aquí https://play.google.com/store/apps/details?id=com.harris.wordcracker
Vea la aplicación en acción en el video https://www.youtube.com/watch?v=DL2974WmNAI
fuente