Este é um dos projetos de pesquisa que estou realizando atualmente. O requisito é quase exatamente igual ao seu, e desenvolvemos algoritmos interessantes para resolver o problema.
A entrada
A entrada é um fluxo infinito de palavras ou frases em inglês (nós as referimos como tokens
).
A saída
- Saída dos principais N tokens que vimos até agora (de todos os tokens que vimos!)
- Produza os N tokens principais em uma janela histórica, digamos, último dia ou semana passada.
Uma aplicação desta pesquisa é encontrar o tema quente ou tendências de assunto no Twitter ou Facebook. Temos um rastreador que rastreia o site, que gera um fluxo de palavras, que vai alimentar o sistema. O sistema, então, produzirá as palavras ou frases de alta frequência de maneira geral ou histórica. Imagine nas últimas semanas a frase "Copa do Mundo" apareceria muitas vezes no Twitter. O mesmo acontece com "Paul, o polvo". :)
String em inteiros
O sistema possui um ID inteiro para cada palavra. Embora haja palavras quase infinitas possíveis na Internet, mas depois de acumular um grande conjunto de palavras, a possibilidade de encontrar novas palavras torna-se cada vez menor. Já encontramos 4 milhões de palavras diferentes e atribuímos um ID exclusivo para cada uma. Todo esse conjunto de dados pode ser carregado na memória como uma tabela hash, consumindo aproximadamente 300 MB de memória. (Implementamos nossa própria tabela de hash. A implementação do Java exige uma grande sobrecarga de memória)
Cada frase pode então ser identificada como um array de inteiros.
Isso é importante porque a classificação e as comparações em inteiros são muito mais rápidas do que em strings.
Dados de arquivo
O sistema mantém os dados do arquivo para cada token. Basicamente, são pares de (Token, Frequency)
. No entanto, a tabela que armazena os dados seria tão grande que teríamos que particionar a tabela fisicamente. Uma vez que o esquema de partição é baseado em ngrams do token. Se o token for uma única palavra, será 1 grama. Se o token for uma frase de duas palavras, será de 2 gramas. E isso continua. Aproximadamente em 4gram, temos 1 bilhão de registros, com a tabela dimensionada em torno de 60 GB.
Processando fluxos de entrada
O sistema absorverá as frases recebidas até que a memória seja totalmente utilizada (Sim, precisamos de um MemoryManager). Depois de pegar N frases e armazená-las na memória, o sistema faz uma pausa e começa a tokenizar cada frase em palavras e frases. Cada token (palavra ou frase) é contado.
Para tokens altamente frequentes, eles são sempre mantidos na memória. Para tokens menos frequentes, eles são classificados com base em IDs (lembre-se de que traduzimos a String em uma matriz de inteiros) e serializados em um arquivo de disco.
(No entanto, para o seu problema, uma vez que você está contando apenas palavras, você pode colocar todos os mapas de frequência de palavras apenas na memória. Uma estrutura de dados cuidadosamente projetada consumiria apenas 300 MB de memória para 4 milhões de palavras diferentes. Alguma dica: use caracteres ASCII para representam Strings), e isso é muito aceitável.
Enquanto isso, haverá outro processo que será ativado assim que encontrar qualquer arquivo de disco gerado pelo sistema e, em seguida, começará a mesclá-lo. Uma vez que o arquivo do disco é classificado, a fusão levaria um processo semelhante, como a classificação por fusão. Alguns projetos também precisam ser cuidados aqui, já que queremos evitar muitas buscas aleatórias de disco. A ideia é evitar ler (processo de mesclagem) / gravação (saída do sistema) ao mesmo tempo e permitir que o processo de mesclagem leia de um disco enquanto grava em um disco diferente. Isso é semelhante a implementar um bloqueio.
Fim do dia
No final do dia, o sistema terá muitos tokens frequentes com frequência armazenados na memória, e muitos outros tokens menos frequentes armazenados em vários arquivos do disco (e cada arquivo é classificado).
O sistema descarrega o mapa da memória em um arquivo de disco (classifique-o). Agora, o problema é mesclar um conjunto de arquivos de disco classificados. Usando um processo semelhante, obteríamos um arquivo de disco classificado no final.
Em seguida, a tarefa final é mesclar o arquivo de disco classificado no banco de dados de arquivo. Depende do tamanho do banco de dados do arquivo, o algoritmo funciona como abaixo se for grande o suficiente:
for each record in sorted disk file
update archive database by increasing frequency
if rowcount == 0 then put the record into a list
end for
for each record in the list of having rowcount == 0
insert into archive database
end for
A intuição é que, depois de algum tempo, o número de inserções ficará cada vez menor. Mais e mais operação será apenas na atualização. E esta atualização não será penalizada por índice.
Espero que toda esta explicação ajude. :)
what is the most frequent item in the subsequence [2; 2; 3; 3; 3; 4; 4; 4; 4; 5; 5] of your sequence?