これはおそらく、コラムニストのJon BentleyがDonald Knuthにファイル内でk個の最も頻繁な単語を見つけるプログラムを書くように依頼した1986年に共鳴した古典的なコーディングの課題の1つです。Knuthは、8ページの長さのプログラムでハッシュトライを使用して、リテラシーのプログラミングテクニックを説明する高速ソリューションを実装しました。Bell LabsのDouglas McIlroyは、Knuthのソリューションが聖書の全文を処理することさえできないと批判し、ワンライナーで答えました。
tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed 10q
1987年には、プリンストン教授による別のソリューションを含むフォローアップ記事が公開されました。しかし、単一の聖書の結果を返すことさえできませんでした!
問題の説明
元の問題の説明:
テキストファイルと整数kが与えられた場合、ファイル内のk個の最も一般的な単語(およびその出現回数)を頻度を減らして出力します。
追加の問題の説明:
- Knuthは単語をラテン文字の文字列として定義しました:
[A-Za-z]+
- 他のすべての文字は無視されます
- 大文字と小文字は同等と見なされます(
WoRd
==word
) - ファイルサイズにも語長にも制限はありません
- 連続する単語間の距離は任意に大きくすることができます
- 最速のプログラムは、合計CPU時間を最小限に抑えるプログラムです(おそらくマルチスレッド化は役に立たないでしょう)
サンプルテストケース
テスト1: James JoyceによるUlyssesは 64回連結しました(96 MBファイル)。
- Project GutenbergからUlyssesをダウンロードします。
wget http://www.gutenberg.org/files/4300/4300-0.txt
- 64回連結します。
for i in {1..64}; do cat 4300-0.txt >> ulysses64; done
- 最も頻繁に使用される単語は「the」で、外観は968832です。
テスト2:特別に生成されたランダムテキストgiganovel
(約1 GB)。
- Python 3ジェネレータースクリプトはこちら。
- テキストには、自然言語と同様に表示される148391個の異なる単語が含まれています。
- 最も頻繁に使用される単語:「e」(11309回出現)および「ihit」(11290回出現)。
一般性テスト:任意の大きなギャップを持つ任意の大きな単語。
参照実装
この問題についてRosettaコードを調べて、多くの実装が非常に遅い(シェルスクリプトよりも遅い!)ことを認識した後、ここでいくつかの優れた実装をテストしました。以下は、ulysses64
時間の複雑さに対するパフォーマンスです。
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)
あなたはそれを打つことができますか?
テスト中
パフォーマンスは、2017 13 "MacBook Proを標準のUnix time
コマンド(「ユーザー」時間)で評価します。可能であれば、最新のコンパイラーを使用してください(たとえば、レガシーではなく最新のHaskellバージョンを使用してください)。
これまでのランキング
参照プログラムを含むタイミング:
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
累積ランキング*(%、最高得点– 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
* 3つのテストのそれぞれにおける最高のプログラムに対する相対的な時間パフォーマンス。
ベストプログラム:ここ。