Entrada: "tableapplechairtablecupboard..."
muchas palabras
¿Cuál sería un algoritmo eficiente para dividir dicho texto en la lista de palabras y obtener:
Salida: ["table", "apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]
Lo primero que me viene a la mente es revisar todas las palabras posibles (comenzando con la primera letra) y encontrar la palabra más larga posible, continúe desde position=word_position+len(word)
PD:
Tenemos una lista de todas las palabras posibles.
La palabra "armario" puede ser "taza" y "tabla", seleccione la más larga.
Idioma: python, pero lo principal es el algoritmo en sí.
['able', 'air', 'apple', 'boa', 'boar', 'board', 'chair', 'cup', 'cupboard', 'ha', 'hair', 'lea', 'leap', 'oar', 'tab', 'table', 'up']
Respuestas:
Un algoritmo ingenuo no dará buenos resultados cuando se aplique a datos del mundo real. Aquí hay un algoritmo de 20 líneas que explota la frecuencia relativa de palabras para brindar resultados precisos para texto de palabras reales.
(Si desea una respuesta a su pregunta original que no usa la frecuencia de palabras, debe refinar qué se entiende exactamente por "palabra más larga": ¿es mejor tener una palabra de 20 letras y diez palabras de 3 letras, o es ¿Es mejor tener cinco palabras de 10 letras? Una vez que establezca una definición precisa, solo tiene que cambiar la línea que define
wordcost
para reflejar el significado deseado).La idea
La mejor forma de proceder es modelar la distribución de la salida. Una buena primera aproximación es asumir que todas las palabras se distribuyen de forma independiente. Entonces solo necesita saber la frecuencia relativa de todas las palabras. Es razonable suponer que siguen la ley de Zipf, que es la palabra con rango n en la lista de palabras que tiene una probabilidad de aproximadamente 1 / ( n log N ) donde N es el número de palabras en el diccionario.
Una vez que haya arreglado el modelo, puede usar la programación dinámica para inferir la posición de los espacios. La oración más probable es la que maximiza el producto de la probabilidad de cada palabra individual y es fácil de calcular con programación dinámica. En lugar de usar directamente la probabilidad, usamos un costo definido como el logaritmo de la inversa de la probabilidad para evitar desbordamientos.
El código
que puedes usar con
Los resultados
Estoy usando este diccionario rápido y sucio de 125k palabras que reuní a partir de un pequeño subconjunto de Wikipedia.
Como puede ver, es esencialmente impecable. La parte más importante es asegurarse de que su lista de palabras haya sido entrenada en un corpus similar al que realmente encontrará, de lo contrario, los resultados serán muy malos.
Mejoramiento
La implementación consume una cantidad lineal de tiempo y memoria, por lo que es razonablemente eficiente. Si necesita más aceleraciones, puede crear un árbol de sufijos a partir de la lista de palabras para reducir el tamaño del conjunto de candidatos.
Si necesita procesar una cadena consecutiva muy grande, sería razonable dividir la cadena para evitar el uso excesivo de memoria. Por ejemplo, puede procesar el texto en bloques de 10000 caracteres más un margen de 1000 caracteres a cada lado para evitar efectos de límites. Esto mantendrá el uso de la memoria al mínimo y es casi seguro que no tendrá ningún efecto en la calidad.
fuente
pip install wordninja
words.txt
contiene "comp": `` `$ grep" ^ comp $ "words.txt comp` `` y está ordenado alfabéticamente. este código asume que está ordenado en una frecuencia de aparición decreciente (que es común para listas de n-gramas como esta). si usa una lista ordenada correctamente, su cadena saldrá bien: `` >>> wordninja.split ('nombre de la empresa donde se emplearon cuando comenzó la fecha') ['nombre', 'la', 'empresa', 'donde', 'bonnie', ' era ',' empleado ',' cuando ',' nosotros ',' empezamos ',' citas '] ``Basado en el excelente trabajo en la respuesta principal , he creado un
pip
paquete para un uso fácil.Para instalar, ejecute
pip install wordninja
.Las únicas diferencias son menores. Esto devuelve un en
list
lugar de astr
, funcionapython3
, incluye la lista de palabras y se divide correctamente incluso si hay caracteres no alfa (como guiones bajos, guiones, etc.).¡Gracias de nuevo a Generic Human!
https://github.com/keredson/wordninja
fuente
Aquí hay una solución mediante la búsqueda recursiva:
rendimientos
fuente
Usando una estructura de datos trie , que contiene la lista de palabras posibles, no sería demasiado complicado hacer lo siguiente:
fuente
"tableprechaun"
luego dividirse"tab"
."tableprechaun"
la coincidencia más larga desde el principio es"table"
, dejar"prechaun"
, que no se puede dividir en palabras del diccionario. Entonces tienes que tomar el partido más corto"tab"
dejándote con un"leprechaun"
.La solución de Unutbu estuvo bastante cerca, pero encuentro el código difícil de leer y no produjo el resultado esperado. La solución de Generic Human tiene el inconveniente de que necesita frecuencias de palabras. No es apropiado para todos los casos de uso.
Aquí hay una solución simple usando un algoritmo Divide and Conquer .
find_words('cupboard')
devolverá en['cupboard']
lugar de['cup', 'board']
(asumiendo quecupboard
,cup
yboard
están en el diccionario)find_words('charactersin')
podría volver['characters', 'in']
o tal vez volverá['character', 'sin']
(como se ve a continuación). Puede modificar fácilmente el algoritmo para devolver todas las soluciones óptimas.El código:
Esto tomará alrededor de 5 segundos en mi máquina de 3GHz:
fuente
La respuesta de https://stackoverflow.com/users/1515832/generic-human es excelente. Pero la mejor implementación de esto que he visto nunca fue escrita por el propio Peter Norvig en su libro 'Beautiful Data'.
Antes de pegar su código, permítanme explicarme por qué el método de Norvig es más preciso (aunque un poco más lento y más largo en términos de código).
1) Los datos son un poco mejores, tanto en términos de tamaño como en términos de precisión (usa un recuento de palabras en lugar de una clasificación simple) 2) Más importante aún, es la lógica detrás de los n-gramas lo que realmente hace que el enfoque sea tan preciso .
El ejemplo que proporciona en su libro es el problema de dividir una cuerda 'sentada'. Ahora, un método de división de cadenas que no es bigrama consideraría p ('sit') * p ('down'), y si es menor que p ('sitdown'), que será el caso con bastante frecuencia, NO se dividirá , pero nos gustaría que lo hiciera (la mayor parte del tiempo).
Sin embargo, cuando tiene el modelo de bigrama, puede valorar p ('sentarse') como un bigrama frente a p ('sentarse') y el primero gana. Básicamente, si no usa bigrams, trata la probabilidad de las palabras que está dividiendo como independientes, lo cual no es el caso, es más probable que algunas palabras aparezcan una tras otra. Desafortunadamente, esas son también las palabras que a menudo se juntan en muchos casos y confunden al divisor.
Aquí está el enlace a los datos (son datos para 3 problemas separados y la segmentación es solo uno. Lea el capítulo para obtener más detalles): http://norvig.com/ngrams/
y aquí está el enlace al código: http://norvig.com/ngrams/ngrams.py
Estos enlaces han estado activos por un tiempo, pero copiaré y pegaré la parte de segmentación del código aquí de todos modos.
fuente
RuntimeError: maximum recursion depth exceeded in cmp
Aquí está la respuesta aceptada traducida a JavaScript (requiere node.js y el archivo "wordninja_words.txt" de https://github.com/keredson/wordninja ):
fuente
Si precompila la lista de palabras en un DFA (que será muy lento), entonces el tiempo que lleva hacer coincidir una entrada será proporcional a la longitud de la cadena (de hecho, solo un poco más lento que simplemente iterar sobre la cadena).
Esta es efectivamente una versión más general del algoritmo trie que se mencionó anteriormente. Solo lo menciono en forma completa: hasta el momento, no existe una implementación de DFA que pueda usar. RE2 funcionaría, pero no sé si los enlaces de Python le permiten ajustar el tamaño que permite que sea un DFA antes de que simplemente elimine los datos compilados de DFA y realice la búsqueda NFA.
fuente
Parece que un retroceso bastante mundano servirá. Empiece por el principio de la cuerda. Escanee a la derecha hasta que tenga una palabra. Luego, llame a la función en el resto de la cadena. La función devuelve "falso" si escanea completamente hacia la derecha sin reconocer una palabra. De lo contrario, devuelve la palabra que encontró y la lista de palabras devuelta por la llamada recursiva.
Ejemplo: "tableapple". Busca "tabulación", luego "salto", pero ninguna palabra en "ple". Ninguna otra palabra en "salto". Busca "tabla", luego "aplicación". "le" ni una palabra, así que intenta Apple, reconoce, vuelve.
Para obtener el mayor tiempo posible, continúe, solo emitiendo (en lugar de devolver) las soluciones correctas; luego, elija el óptimo según cualquier criterio que elija (maxmax, minmax, average, etc.)
fuente
Basado en la solución de unutbu, he implementado una versión de Java:
Entrada:
"tableapplechairtablecupboard"
Salida:
[table, apple, chair, table, cupboard]
Entrada:
"tableprechaun"
Salida:
[tab, leprechaun]
fuente
Para el idioma alemán, existe CharSplit, que utiliza el aprendizaje automático y funciona bastante bien para cadenas de unas pocas palabras.
https://github.com/dtuggener/CharSplit
fuente
Ampliando la sugerencia de @ miku de usar a
Trie
, un append-onlyTrie
es relativamente sencillo de implementar enpython
:Luego podemos construir un
Trie
diccionario basado en un conjunto de palabras:Lo que producirá un árbol que se ve así (
*
indica el comienzo o el final de una palabra):Podemos incorporar esto en una solución combinándolo con una heurística sobre cómo elegir palabras. Por ejemplo, podemos preferir palabras más largas a palabras más cortas:
Podemos usar esta función así:
Porque mantenemos nuestra posición en la
Trie
medida que la búsqueda por más tiempo y las palabras más largas, se recorre eltrie
máximo una vez por cada posible solución (en lugar de2
veces parapeanut
:pea
,peanut
). El cortocircuito final nos salva de caminar como carbón a través de la cuerda en el peor de los casos.El resultado final es solo un puñado de inspecciones:
Una ventaja de esta solución es el hecho de que sabe muy rápidamente si existen palabras más largas con un prefijo determinado, lo que evita la necesidad de probar exhaustivamente las combinaciones de secuencias en un diccionario. También hace que llegar a una
unsolvable
respuesta sea comparativamente más económico que otras implementaciones.Las desventajas de esta solución son una gran huella de memoria para el
trie
y el costo de construccióntrie
inicial.fuente
Si tiene una lista exhaustiva de las palabras contenidas en la cadena:
word_list = ["table", "apple", "chair", "cupboard"]
Usar la comprensión de listas para iterar sobre la lista para localizar la palabra y cuántas veces aparece.
La función devuelve una
string
salida de palabras en el orden de la lista.table table apple chair cupboard
fuente
Muchas gracias por la ayuda en https://github.com/keredson/wordninja/
Una pequeña contribución de lo mismo en Java de mi parte.
El método público
splitContiguousWords
podría integrarse con los otros 2 métodos de la clase que tiene ninja_words.txt en el mismo directorio (o modificarse según la elección del codificador). Y el métodosplitContiguousWords
podría usarse para ese propósito.fuente
public
método acepta una oración de tipoString
que se divide en función de un primer nivel con expresiones regulares. Y para ver la listaninja_words
, está disponible para descargar desde el repositorio de git.Esto ayudará
fuente
Necesita identificar su vocabulario, tal vez cualquier lista de palabras gratuita sea suficiente.
Una vez hecho esto, use ese vocabulario para construir un árbol de sufijos y haga coincidir su flujo de entrada con eso: http://en.wikipedia.org/wiki/Suffix_tree
fuente