web-dev-qa-db-ja.com

ファイルを指定して、最も頻繁に出現する10個の単語をできるだけ効率的に見つけます

これは明らかに面接の質問です(面接の質問のコレクションで見つかりました)が、そうでなくてもかなりクールです。

これは、すべての複雑さの測定で効率的に行うように指示されています。単語を頻度にマッピングするHashMapを作成することを考えました。時間と空間の複雑さはO(n)ですが、単語がたくさんある可能性があるため、すべてをメモリに格納できるとは限りません。

質問には、単語をメモリに保存できないとは何も書かれていませんが、その場合はどうなりますか?そうでない場合、質問はそれほど難しいようには見えません。

21

自分の時間に合わせて最適化する:

sort file | uniq -c | sort -nr | head -10

おそらくその後にawk '{print $2}'カウントを削除します。

19
Ben Jackson

トライデータ構造 が選択だと思います。

トライでは、ルートから現在のノードへのパス上の文字で構成される単語の頻度を表す各ノードの単語数を記録できます。

トライを設定するための時間計算量はO(Ln)〜O(n)(ここで、Lは最長の単語の文字数です。定数として扱います。上位10語を見つけるには、トライをトラバースできますが、これにもO(n)がかかります。したがって、この問題を解決するにはO(n)]が必要です。

12

完全な解決策は次のようになります。

  1. 外部ソーティングを行うO(N log N)
  2. ファイルO(N)の単語頻度を数える
  3. (別の方法は、@ Summer_More_More_TeaとしてTrieを使用して、その量のメモリに余裕がある場合は、頻度をカウントすることです)O(k * N)// 2つの最初のステップ
  4. 最小ヒープを使用します:
    • 最初のn個の要素をヒープに配置します
    • 残っているすべての単語について、それをヒープに追加し、ヒープ内の新しい最小値を削除します
    • 最終的に、ヒープにはn番目に一般的な単語O(| words | * log(n))が含まれます。

Trieを使用すると、総単語数は一般に語彙のサイズよりも大きいため、コストはO(k * N)になります。最後に、ほとんどの西洋言語ではkが小さいため、線形の複雑さを想定できます。

3
Alessandro

26個のアルファベットのそれぞれにランダムな素数を割り当てたとしましょう。次に、ファイルをスキャンします。単語が見つかるたびに、そのハッシュ値(位置と単語を構成するアルファベットの値に基づく式)を計算します。ハッシュテーブルでこの値が見つかった場合、初めてこの値に遭遇していないことが確実にわかり、そのキー値をインクリメントします。そして、最大10の配列を維持します。ただし、新しいハッシュが見つかった場合は、そのハッシュ値のファイルポインターを格納し、キーを0に初期化します。

2
amol_beast

私はこのように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);
2
user372724

時間と空間のトレードオフを行い、時間の場合はO(n^2)に、(メモリ)空間の場合はO(1)に進むことができます。これは、Wordがデータ。カウントがこれまでに見つかったトップ10を上回っている場合は、Wordとカウントを保持し、そうでない場合は無視します。

1
user470379

ハッシュを作成して値を並べ替えるのが最善だと言います。私は同意する傾向があります。 http://www.allinterview.com/showanswers/56657.html

これは似たようなことをするBashの実装です...私は思う http://www.commandlinefu.com/commands/view/5994/computes-the-most-frequent-used-words-of-a-テキストファイル

1
EnabrenTane

入力データのサイズに応じて、HashMapを保持することをお勧めする場合としない場合があります。たとえば、ハッシュマップが大きすぎてメインメモリに収まらないとします。ほとんどのハッシュマップ実装はランダムアクセスを必要とし、キャッシュではあまり適切ではないため、これにより非常に多くのメモリ転送が発生する可能性があります。

このような場合、入力データを並べ替えることがより良い解決策になります。

1
Sanjit Saluja

各単語の出現の合計は単語の総数に等しいので、これはソートを数える典型的なアプリケーションだと思います。カウントソートを備えたハッシュテーブルは、単語数に比例した時間でジョブを実行する必要があります。

1
Aly Farahat

ステップ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

0
raluca

基数木 またはそのバリエーションの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;
            }
        }
    }
0
Amit Bose

単語の文字列を循環し、それぞれを辞書に保存し(Pythonを使用)、それらが値として出現する回数。

0
a sandwhich

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
ENDfileに達すると、
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秒で完了しました。

0
Chris Koknat

Wordリストがメモリに収まらない場合は、収まるまでファイルを分割できます。各部分のヒストグラムを(順次または並列に)生成し、結果をマージします(すべての入力の正確性を保証したい場合は、詳細が少し面倒かもしれませんが、O(n)努力、またはO(n/k) kタスクの時間)。

0
comingstorm