これは明らかに面接の質問です(面接の質問のコレクションで見つかりました)が、そうでなくてもかなりクールです。
これは、すべての複雑さの測定で効率的に行うように指示されています。単語を頻度にマッピングするHashMapを作成することを考えました。時間と空間の複雑さはO(n)ですが、単語がたくさんある可能性があるため、すべてをメモリに格納できるとは限りません。
質問には、単語をメモリに保存できないとは何も書かれていませんが、その場合はどうなりますか?そうでない場合、質問はそれほど難しいようには見えません。
自分の時間に合わせて最適化する:
sort file | uniq -c | sort -nr | head -10
おそらくその後にawk '{print $2}'
カウントを削除します。
トライデータ構造 が選択だと思います。
トライでは、ルートから現在のノードへのパス上の文字で構成される単語の頻度を表す各ノードの単語数を記録できます。
トライを設定するための時間計算量はO(Ln)〜O(n)(ここで、Lは最長の単語の文字数です。定数として扱います。上位10語を見つけるには、トライをトラバースできますが、これにもO(n)がかかります。したがって、この問題を解決するにはO(n)]が必要です。
完全な解決策は次のようになります。
Trieを使用すると、総単語数は一般に語彙のサイズよりも大きいため、コストはO(k * N)になります。最後に、ほとんどの西洋言語ではkが小さいため、線形の複雑さを想定できます。
26個のアルファベットのそれぞれにランダムな素数を割り当てたとしましょう。次に、ファイルをスキャンします。単語が見つかるたびに、そのハッシュ値(位置と単語を構成するアルファベットの値に基づく式)を計算します。ハッシュテーブルでこの値が見つかった場合、初めてこの値に遭遇していないことが確実にわかり、そのキー値をインクリメントします。そして、最大10の配列を維持します。ただし、新しいハッシュが見つかった場合は、そのハッシュ値のファイルポインターを格納し、キーを0に初期化します。
私はこのようにC#で行いました(サンプル)
int wordFrequency = 10;
string words = "hello how r u u u u u u u u u u u u u u u u u u ? hello there u u u u ! great to c u there. hello .hello hello hello hello hello .hello hello hello hello hello hello ";
var result = (from Word in words.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries)
group Word by Word into g
select new { Word = g.Key, Occurance = g.Count() }).ToList().FindAll(i => i.Occurance >= wordFrequency);
時間と空間のトレードオフを行い、時間の場合はO(n^2)
に、(メモリ)空間の場合はO(1)
に進むことができます。これは、Wordがデータ。カウントがこれまでに見つかったトップ10を上回っている場合は、Wordとカウントを保持し、そうでない場合は無視します。
ハッシュを作成して値を並べ替えるのが最善だと言います。私は同意する傾向があります。 http://www.allinterview.com/showanswers/56657.html
これは似たようなことをするBashの実装です...私は思う http://www.commandlinefu.com/commands/view/5994/computes-the-most-frequent-used-words-of-a-テキストファイル
入力データのサイズに応じて、HashMapを保持することをお勧めする場合としない場合があります。たとえば、ハッシュマップが大きすぎてメインメモリに収まらないとします。ほとんどのハッシュマップ実装はランダムアクセスを必要とし、キャッシュではあまり適切ではないため、これにより非常に多くのメモリ転送が発生する可能性があります。
このような場合、入力データを並べ替えることがより良い解決策になります。
各単語の出現の合計は単語の総数に等しいので、これはソートを数える典型的なアプリケーションだと思います。カウントソートを備えたハッシュテーブルは、単語数に比例した時間でジョブを実行する必要があります。
ステップ1:ファイルが非常に大きく、メモリ内で並べ替えることができない場合は、メモリ内で並べ替えることができるチャンクに分割できます。
ステップ2:ソートされたチャンクごとに(words、nr_occurrence)のソートされたペアを計算します。ソートされたペアのみが必要なため、彼の時点でチャンクを放棄できます。
ステップ:チャンクを反復処理してチャンクを並べ替え、常に上位10の外観を維持します。
例:
ステップ1:
a b a ab abb a a b b c c ab ab
割る :
チャンク1:a b a ab
チャンク2:abb a a b b
チャンク3:c c ab ab
ステップ2:
チャンク1:a2、b1、ab1チャンク2:a2、b2、abb1
チャンク3:c2、ab2
ステップ(チャンクをマージして上位10の外観を維持します):
a4 b3 ab3 c2 abb1
基数木 またはそのバリエーションの1つを使用すると、通常、一般的なシーケンスを折りたたむことでストレージスペースを節約できます。
ビルドにはO(nk)-ここで、kは「セット内のすべての文字列の最大長」です。
int k = 0;
int n = i;
int j;
string[] stringList = h.Split(" ".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries);
int m = stringList.Count();
for (j = 0; j < m; j++)
{
int c = 0;
for (k = 0; k < m; k++)
{
if (string.Compare(stringList[j], stringList[k]) == 0)
{
c = c + 1;
}
}
}
単語の文字列を循環し、それぞれを辞書に保存し(Pythonを使用)、それらが値として出現する回数。
CPUに関しては最も効率的ではなく、醜いですが、たった2分で完了しました。
Perl -lane '$h{$_}++ for @F; END{for $w (sort {$h{$b}<=>$h{$a}} keys %h) {print "$h{$w}\t$w"}}' file | head
-n
で各行をループします
各行を@F
で-a
単語に分割します
各$_
単語はハッシュをインクリメントします%h
END
のfile
に達すると、sort
頻度によるハッシュ
頻度$h{$w}
と単語$w
を印刷します
bash head
を使用して10行で停止します
このWebページのテキストを入力として使用する:
121 the
77 a
48 in
46 to
44 of
39 at
33 is
30 vote
29 and
25 you
このソリューションとトップクラスのシェルソリューション(ben jackson)を、580,000,000語の3.3GBテキストファイルでベンチマークしました。
Perl 5.22は171秒で完了しましたが、シェルソリューションは474秒で完了しました。
Wordリストがメモリに収まらない場合は、収まるまでファイルを分割できます。各部分のヒストグラムを(順次または並列に)生成し、結果をマージします(すべての入力の正確性を保証したい場合は、詳細が少し面倒かもしれませんが、O(n)努力、またはO(n/k) kタスクの時間)。