Esse talvez seja um dos desafios clássicos de codificação que tiveram alguma ressonância em 1986, quando o colunista Jon Bentley pediu a Donald Knuth que escrevesse um programa que encontrasse k palavras mais frequentes em um arquivo. Knuth implementou uma solução rápida usando tentativas de hash em um programa de 8 páginas para ilustrar sua técnica de programação. Douglas McIlroy, do Bell Labs, criticou a solução de Knuth por não conseguir processar um texto completo da Bíblia e respondeu com uma única frase, que não é tão rápida, mas faz o trabalho:
tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed 10q
Em 1987, um artigo de acompanhamento foi publicado com mais uma solução, desta vez por um professor de Princeton. Mas também não conseguiu retornar o resultado para uma única Bíblia!
Descrição do Problema
Descrição original do problema:
Dado um arquivo de texto e um número inteiro k, você deve imprimir as k palavras mais comuns no arquivo (e o número de ocorrências) em frequência decrescente.
Esclarecimentos adicionais sobre problemas:
- Knuth definiu as palavras como uma sequência de letras latinas:
[A-Za-z]+
- todos os outros caracteres são ignorados
- letras maiúsculas e minúsculas são consideradas equivalentes (
WoRd
==word
) - sem limite de tamanho de arquivo nem comprimento de palavra
- distâncias entre palavras consecutivas podem ser arbitrariamente grandes
- O programa mais rápido é o que utiliza menos tempo total de CPU (o multithreading provavelmente não ajudará)
Casos de teste de amostra
Teste 1: Ulisses de James Joyce concatenou 64 vezes (arquivo de 96 MB).
- Faça o download de Ulisses do Projeto Gutenberg:
wget http://www.gutenberg.org/files/4300/4300-0.txt
- Concatene 64 vezes:
for i in {1..64}; do cat 4300-0.txt >> ulysses64; done
- A palavra mais frequente é "the" com 968832 aparências.
Teste 2: texto aleatório gerado especialmente giganovel
(cerca de 1 GB).
- Script do gerador Python 3 aqui .
- O texto contém 148391 palavras distintas que aparecem de maneira semelhante aos idiomas naturais.
- Palavras mais frequentes: “e” (11309 aparições) e “ihit” (11290 aparições).
Teste de generalidade: palavras arbitrariamente grandes com lacunas arbitrariamente grandes.
Implementações de referência
Depois de analisar o Rosetta Code para esse problema e perceber que muitas implementações são incrivelmente lentas (mais lentas que o shell script!), Testei algumas boas implementações aqui . Abaixo está o desempenho para, ulysses64
juntamente com a complexidade do tempo:
ulysses64 Time complexity
C++ (prefix trie + heap) 4.145 O((N + k) log k)
Python (Counter) 10.547 O(N + k log Q)
AWK + sort 20.606 O(N + Q log Q)
McIlroy (tr + sort + uniq) 43.554 O(N log N)
Você pode vencer isso?
Teste
O desempenho será avaliado usando o MacBook Pro de 13 "de 2017 com o padrão Unix time
comando (horário do" usuário "). Se possível, use compiladores modernos (por exemplo, use a versão mais recente do Haskell, e não a legada).
Rankings até agora
Horários, incluindo os programas de referência:
k=10 k=100K
ulysses64 giganovel giganovel
C (trie + bins) by Moogie 0.704 9.568 9.459
C (trie + list) by Moogie 0.767 6.051 82.306
C (trie + sorted list) by Moogie 0.804 7.076 x
Rust (trie) by Anders Kaseorg 0.842 6.932 7.503
J by miles 1.273 22.365 22.637
C# (trie) by recursive 3.722 25.378 24.771
C++ (trie + heap) 4.145 42.631 72.138
APL (Dyalog Unicode) by Adám 7.680 x x
Python (dict) by movatica 9.387 99.118 100.859
Python (Counter) 10.547 102.822 103.930
Ruby (tally) by daniero 15.139 171.095 171.551
AWK + sort 20.606 213.366 222.782
McIlroy (tr + sort + uniq) 43.554 715.602 750.420
Classificação acumulada * (%, melhor pontuação possível - 300):
# Program Score Generality
1 Rust (trie) by Anders Kaseorg 334 Yes
2 C (trie + bins) by Moogie 384 x
3 J by miles 852 Yes
4 C# (trie) by recursive 1278 x
5 C (trie + list) by Moogie 1306 x
6 C++ (trie + heap) 2255 x
7 Python (dict) by movatica 4316 Yes
8 Python (Counter) 4583 Yes
9 Ruby (tally) by daniero 7264 Yes
10 AWK + sort 9422 Yes
11 McIlroy (tr + sort + uniq) 28014 Yes
* Soma do desempenho do tempo em relação aos melhores programas em cada um dos três testes.
Melhor programa: aqui .