Entrada: "tableapplechairtablecupboard..."
muitas palavras
Qual seria um algoritmo eficiente para dividir esse texto na lista de palavras e obter:
Resultado: ["table", "apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]
A primeira coisa que vem à mente é percorrer todas as palavras possíveis (começando com a primeira letra) e encontrar a palavra mais longa possível, continuar de position=word_position+len(word)
PS
Temos uma lista de todas as palavras possíveis.
A palavra "armário" pode ser "xícara" e "tábua", selecione a mais longa.
Linguagem: python, mas o principal é o próprio algoritmo.
['able', 'air', 'apple', 'boa', 'boar', 'board', 'chair', 'cup', 'cupboard', 'ha', 'hair', 'lea', 'leap', 'oar', 'tab', 'table', 'up']
Respostas:
Um algoritmo ingênuo não dará bons resultados quando aplicado a dados do mundo real. Aqui está um algoritmo de 20 linhas que explora a frequência relativa de palavras para fornecer resultados precisos para texto de palavras reais.
(Se você quiser uma resposta à sua pergunta original que não use a frequência de palavras, você precisa refinar o que exatamente significa "palavra mais longa": é melhor ter uma palavra de 20 letras e dez palavras de 3 letras, ou é é melhor ter cinco palavras de 10 letras? Depois de estabelecer uma definição precisa, você só precisa alterar a definição de linha
wordcost
para refletir o significado pretendido.)A ideia
A melhor maneira de proceder é modelar a distribuição da saída. Uma boa primeira aproximação é assumir que todas as palavras são distribuídas independentemente. Então você só precisa saber a frequência relativa de todas as palavras. É razoável supor que eles sigam a lei de Zipf, ou seja, a palavra com classificação n na lista de palavras tem probabilidade de aproximadamente 1 / ( n log N ), onde N é o número de palavras no dicionário.
Depois de corrigir o modelo, você pode usar a programação dinâmica para inferir a posição dos espaços. A frase mais provável é aquela que maximiza o produto da probabilidade de cada palavra individual, e é fácil computá-la com programação dinâmica. Em vez de usar diretamente a probabilidade, usamos um custo definido como o logaritmo do inverso da probabilidade para evitar overflows.
O código
que você pode usar com
Os resultados
Estou usando este dicionário rápido e sujo de 125 mil palavras que reuni de um pequeno subconjunto da Wikipedia.
Como você pode ver, é essencialmente perfeito. A parte mais importante é certificar-se de que sua lista de palavras foi treinada para um corpus semelhante ao que você realmente encontrará, caso contrário, os resultados serão muito ruins.
Otimização
A implementação consome uma quantidade linear de tempo e memória, por isso é razoavelmente eficiente. Se precisar de mais acelerações, você pode construir uma árvore de sufixos a partir da lista de palavras para reduzir o tamanho do conjunto de candidatos.
Se você precisar processar uma string consecutiva muito grande, seria razoável dividir a string para evitar o uso excessivo de memória. Por exemplo, você pode processar o texto em blocos de 10.000 caracteres mais uma margem de 1.000 caracteres em cada lado para evitar efeitos de limite. Isso manterá o uso de memória no mínimo e quase certamente não terá efeito na qualidade.
fonte
pip install wordninja
words.txt
contém "comp": `` `$ grep" ^ comp $ "words.txt comp` `` e está classificado em ordem alfabética. este código assume que está classificado em frequência decrescente de aparecimento (o que é comum para listas de n-gram como esta). se você usar uma lista devidamente classificada, sua string sairá bem: `` `>>> wordninja.split ('namethecompanywherebonniewasemployedwhenwestarteddating') ['name', 'the', 'company', 'where', 'bonnie', ' estava ',' empregado ',' quando ',' nós ',' começado ',' namorando '] `` `Com base no excelente trabalho da primeira resposta , criei um
pip
pacote para fácil uso.Para instalar, execute
pip install wordninja
.As únicas diferenças são pequenas. Isso retorna um em
list
vez de umstr
, ele funciona empython3
, inclui a lista de palavras e se divide adequadamente, mesmo se houver caracteres não alfa (como sublinhados, travessões, etc.).Obrigado mais uma vez a Generic Human!
https://github.com/keredson/wordninja
fonte
Aqui está a solução usando pesquisa recursiva:
rendimentos
fonte
Usando uma estrutura de dados trie , que contém a lista de palavras possíveis, não seria muito complicado fazer o seguinte:
fonte
"tableprechaun"
depois deverá ser dividido"tab"
."tableprechaun"
a correspondência mais longa desde o início é"table"
, sair"prechaun"
, que não pode ser dividido em palavras do dicionário. Então você tem que pegar a partida mais curta,"tab"
deixando você com um"leprechaun"
.A solução de Unutbu foi bem próxima, mas acho o código difícil de ler e não produziu o resultado esperado. A solução da Generic Human tem a desvantagem de precisar de frequências de palavras. Não é apropriado para todos os casos de uso.
Aqui está uma solução simples usando um algoritmo Divide and Conquer .
find_words('cupboard')
irá retornar['cupboard']
ao invés de['cup', 'board']
(assumindo quecupboard
,cup
eboard
estão no dicionário)find_words('charactersin')
pode voltar['characters', 'in']
ou talvez volte['character', 'sin']
(como visto abaixo). Você pode facilmente modificar o algoritmo para retornar todas as soluções ideais.O código:
Isso levará cerca de 5 segundos na minha máquina 3GHz:
fonte
A resposta por https://stackoverflow.com/users/1515832/generic-human é ótima. Mas a melhor implementação disso que já vi foi escrita pelo próprio Peter Norvig em seu livro 'Beautiful Data'.
Antes de colar seu código, deixe-me explicar por que o método de Norvig é mais preciso (embora um pouco mais lento e longo em termos de código).
1) Os dados são um pouco melhores - tanto em termos de tamanho quanto em termos de precisão (ele usa uma contagem de palavras em vez de uma classificação simples) 2) Mais importante, é a lógica por trás de n-gramas que realmente torna a abordagem tão precisa .
O exemplo que ele fornece em seu livro é o problema de dividir uma string 'sitdown'. Agora, um método não bigrama de divisão de string consideraria p ('sentar') * p ('para baixo'), e se for menor que p ('sentar') - o que será o caso com bastante frequência - NÃO irá dividir , mas gostaríamos que fizesse (na maioria das vezes).
No entanto, quando você tem o modelo de bigrama, você pode avaliar p ('sentar') como um bigrama versus p ('sentar') e o anterior ganha. Basicamente, se você não usar bigramas, ele trata a probabilidade das palavras que você está dividindo como independentes, o que não é o caso, algumas palavras têm mais probabilidade de aparecer uma após a outra. Infelizmente, essas também são as palavras que costumam ficar juntas em muitos casos e confundem o divisor.
Aqui está o link para os dados (são dados para 3 problemas separados e segmentação é apenas um. Leia o capítulo para obter detalhes): http://norvig.com/ngrams/
e aqui está o link para o código: http://norvig.com/ngrams/ngrams.py
Esses links já estão no ar há algum tempo, mas vou copiar e colar a parte de segmentação do código aqui mesmo assim
fonte
RuntimeError: maximum recursion depth exceeded in cmp
Aqui está a resposta aceita traduzida para JavaScript (requer node.js e o arquivo "wordninja_words.txt" de https://github.com/keredson/wordninja ):
fonte
Se você pré-compilar a lista de palavras em um DFA (o que será muito lento), o tempo que leva para corresponder a uma entrada será proporcional ao comprimento da string (na verdade, apenas um pouco mais lento do que apenas iterar sobre a string).
Esta é efetivamente uma versão mais geral do algoritmo trie que foi mencionado anteriormente. Menciono apenas para completar - até o momento, não há nenhuma implementação do DFA que você possa usar. RE2 funcionaria, mas não sei se as ligações Python permitem que você ajuste o tamanho que permite que um DFA seja antes que ele simplesmente jogue fora os dados compilados do DFA e faça a pesquisa NFA.
fonte
Parece que um retrocesso bastante mundano bastará. Comece no início da corda. Leia direito até que você tenha uma palavra. Em seguida, chame a função no resto da string. A função retorna "falso" se varrer todo o caminho para a direita sem reconhecer uma palavra. Caso contrário, retorna a palavra encontrada e a lista de palavras retornadas pela chamada recursiva.
Exemplo: "tableapple". Encontra "tab", depois "leap", mas nenhuma palavra em "ple". Nenhuma outra palavra em "salto". Encontra "mesa", depois "app". "le" nem uma palavra, então tenta maçã, reconhece, retorna.
Para obter o maior tempo possível, continue, apenas emitindo (em vez de retornar) soluções corretas; em seguida, escolha o ideal por qualquer critério de sua escolha (maxmax, minmax, média, etc.)
fonte
Com base na solução da unutbu, implementei uma versão Java:
Entrada:
"tableapplechairtablecupboard"
Resultado:
[table, apple, chair, table, cupboard]
Entrada:
"tableprechaun"
Resultado:
[tab, leprechaun]
fonte
Para o idioma alemão, existe o CharSplit, que usa aprendizado de máquina e funciona muito bem com strings de poucas palavras.
https://github.com/dtuggener/CharSplit
fonte
Expandindo a sugestão de @miku para usar um
Trie
, um append-onlyTrie
é relativamente simples de implementar empython
:Podemos então construir um
Trie
dicionário baseado em um conjunto de palavras:O que produzirá uma árvore semelhante a esta (
*
indica o início ou o fim de uma palavra):Podemos incorporar isso a uma solução combinando-o com uma heurística sobre como escolher as palavras. Por exemplo, podemos preferir palavras mais longas a palavras mais curtas:
Podemos usar essa função assim:
Porque mantemos a nossa posição no
Trie
como nós procurar mais e palavras mais longas, nós percorremos atrie
no máximo uma vez por solução possível (em vez de2
vezes parapeanut
:pea
,peanut
). O curto-circuito final nos salva de andar na corda bamba no pior caso.O resultado final é apenas um punhado de inspeções:
Um benefício dessa solução é o fato de que você sabe muito rapidamente se existem palavras mais longas com um determinado prefixo, o que evita a necessidade de testar exaustivamente as combinações de sequência em um dicionário. Também faz com que chegar a um
unsolvable
resposta comparativamente mais barata do que outras implementações.As desvantagens desta solução são uma grande pegada de memória para o
trie
e o custo de construção dotrie
início.fonte
Se você tiver uma lista exaustiva das palavras contidas na string:
word_list = ["table", "apple", "chair", "cupboard"]
Usando a compreensão de lista para iterar sobre a lista para localizar a palavra e quantas vezes ela aparece.
A função retorna uma
string
saída de palavras na ordem da listatable table apple chair cupboard
fonte
Muito obrigado pela ajuda em https://github.com/keredson/wordninja/
Uma pequena contribuição do mesmo em Java da minha parte.
O método público
splitContiguousWords
pode ser incorporado com os outros 2 métodos na classe com ninja_words.txt no mesmo diretório (ou modificado de acordo com a escolha do codificador). E o métodosplitContiguousWords
pode ser usado para esse propósito.fonte
public
método aceita uma sentença do tipoString
que é dividida com base em um primeiro nível com regex. E a listaninja_words
está disponível para download no repositório git.Isso vai ajudar
fonte
Você precisa identificar seu vocabulário - talvez qualquer lista de palavras livre sirva.
Uma vez feito isso, use esse vocabulário para construir uma árvore de sufixos e compare seu fluxo de entrada com aquele: http://en.wikipedia.org/wiki/Suffix_tree
fonte