これは、1986年にドナルドクヌースが 8ページの長さのプログラムでハッシュトライを使用して高速なソリューションを実装し、リテラシーのプログラミングテクニックを説明する古典的な問題です。一方、Unixパイプのゴッドファーザーであるダグマキロイはワンライナー、それはそれほど速くはありませんでしたが、仕事をやり遂げました:
tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed 10q
もちろん、McIlroyのソリューションには時間の複雑さO(N log N)があります。ここで、Nは単語の総数です。より高速なソリューションがあります。例えば:
これは、上限時間の複雑さO((N + k)log k)を持つC ++実装で、通常はほぼ線形です。
以下は、ハッシュ辞書と時間複雑度O(N + k log Q)のヒープを使用した高速Python実装です。ここで、Qは一意の単語の数です。
import collections, re, sys
filename = sys.argv[1]
k = int(sys.argv[2]) if len(sys.argv)>2 else 10
text = open(filename).read()
counts = collections.Counter(re.findall('[a-z]+', text.lower()))
for i, w in counts.most_common(k):
print(i, w)
CPU時間の比較(秒単位):
bible32 bible256
C++ (prefix tree + heap) 5.659 44.730
Python (Counter) 10.314 100.487
Sheharyar (AWK + sort) 30.864 251.301
McIlroy (tr + sort + uniq) 60.531 690.906
ノート:
- bible32は、それ自体と32回連結された聖書(135 MB)、それぞれbible256〜256回(1.1 GB)です。
- Pythonスクリプトの非線形スローダウンは、純粋にメモリ内のファイルを完全に処理するために発生します。そのため、巨大なファイルのオーバーヘッドが大きくなっています。
- ヒープを構築し、ヒープの最上部からn個の要素を選択できるUnixツールがあれば、AWKソリューションはほぼ線形の時間の複雑さを達成できますが、現在はO(N + Q log Q)です。