ファイル内でn個の最も頻繁な単語を見つける


34

たとえば、テキストファイルで最も一般的な10個の単語を見つけたいです。まず、キーストローク(つまり、私の時間)に合わせてソリューションを最適化する必要があります。第二に、パフォーマンスのため。ここに私がトップ10を獲得するために持っているものがあります:

cat test.txt | tr -c '[:alnum:]' '[\n*]' | uniq -c | sort -nr | head  -10
  6 k
  2 g
  2 e
  2 a
  1 r
  1 k22
  1 k
  1 f
  1 eeeeeeeeeeeeeeeeeeeee
  1 d

Java、Pythonなどのプログラムを作成して、(word、numberOfOccurences)を辞書に保存して値をソートするか、MapReduceを使用できますが、キーストロークを最適化します。

誤検知はありますか?もっと良い方法はありますか?


なぜあなたは最後に-10を置くのですか?:P
アヌ

回答:


47

これは、「N個の最も一般的なもの」を見つける最も一般的な方法です。ただし、が欠落しているsort場合、および無償の場合がありますcat

tr -c '[:alnum:]' '[\n*]' < test.txt | sort | uniq -c | sort -nr | head  -10

sort前に入力しなかった場合、uniq -c おそらく多くの偽のシングルトン単語が表示されます。 uniq全体的な一意性ではなく、一意の行の実行のみを行います。

編集:「ストップワード」というトリックを忘れました。英語のテキスト(ここでは申し訳ありませんが、北米の単言語)を見る場合、「of」、「and」、「the」などの単語はほとんどの場合、上位2位または3位になります。おそらくそれらを排除したいでしょう。GNU Groffディストリビューションにはeign、かなりまともなストップワードのリストを含む名前のファイルがあります。私のアーチのディストリビューションではあり/usr/share/groff/current/eignますが、私は、私も見てきたと思います/usr/share/dict/eign/usr/dict/eign古いのUnixで。

次のようなストップワードを使用できます。

tr -c '[:alnum:]' '[\n*]' < test.txt |
fgrep -v -w -f /usr/share/groff/current/eign |
sort | uniq -c | sort -nr | head  -10

私の推測では、ほとんどの人間の言語では、意味のある単語の頻度カウントから同様の「ストップワード」を削除する必要がありますが、他の言語のストップワードリストを取得する場所はわかりません。

編集: 単語全体の一致を有効にするコマンドfgrepを使用する必要が-wあります。これにより、「a」や「i」のように、短いストップ作品のみを含む単語の誤検知を回避できます。


2
catかなりのパフォーマンスオーバーヘッドが追加されますか?私はパイプ構文が好きです。'[\ n *]'の*は何をしますか?
ルカシュマドン

1
「cat test.txt」が気に入ったら、ぜひ使ってください。デニスリッチーが「cat something | somethingelse」構文がより広く使用され、「<some」構文は単一の目的であるために間違いであると言っている記事を読みました。
ブルースエディガー

find出力で最も一般的なディレクトリ名を見つけたい場合はどうすればよいですか?つまり/、空白文字などの代わりに単語を分割します。
-erb

1
@erb-あなたはおそらく次のようなことをするでしょう:find somewhere optoins | tr '/' '\n' | sort | uniq -c | sort -k1.1nr | head -10
ブルース・エディガー

1
@erb-コメントではなく質問として質問する。必要な答えを得るために、質問を組み立てる余地があります。サンプルの入力と必要な出力を提供します。あなたは良い質問をすることで評判ポイントを得るかもしれません、そして、私はコメントで私ができるよりも良い答えを与えるためにポイントを得ます。
ブルースエディガー16


7

AWKを使用しましょう!

この関数は、提供されたファイルで発生する各単語の頻度を降順でリストします。

function wordfrequency() {
  awk '
     BEGIN { FS="[^a-zA-Z]+" } {
         for (i=1; i<=NF; i++) {
             word = tolower($i)
             words[word]++
         }
     }
     END {
         for (w in words)
              printf("%3d %s\n", words[w], w)
     } ' | sort -rn
}

ファイルで次のように呼び出すことができます。

$ cat your_file.txt | wordfrequency

上位10語については:

$ cat your_file.txt | wordfrequency | head -10

出典:AWK-ward Ruby


4

Haskellを使用しましょう!

これは言語戦争に変わりつつありますよね?

import Data.List
import Data.Ord

main = interact $ (=<<) (\x -> show (length x) ++ " - " ++ head x ++ "\n")
                . sortBy (flip $ comparing length)
                . group . sort
                . words

使用法:

cat input | wordfreq

代わりに:

cat input | wordfreq | head -10

大文字小文字を区別しない修正版:pastebin.com/57T5B6BY
Axel Latvala

クラシックよりもずっと遅くなりますsort | uniq -c | sort -nr
Andriy Makukha

@AndriyMakukhaボトルネックは、文字列がHaskellの文字のリンクリストであるということです。に切り替えるTextか、ByteString代わりにCのような速度を得ることができます。これは、修飾されたものをインポートし、関数の前に修飾子を付けるのと同じくらい簡単です。
ブラックキャップ

pastebin.com/QtJjQwT9大幅に高速化バージョンは、読みやすくするために書かれた
BlackCap

3

このような何かは、一般的に利用可能なPythonを使用して動作するはずです:

cat slowest-names.log | python -c 'import collections, sys; print collections.Counter(sys.stdin);'

これは、行ごとの単語を前提としています。さらにある場合は、分割も簡単になります。


python3以上の出力cat README.md | python -c 'import collections, sys, pprint; pprint.pprint(collections.Counter(sys.stdin));'
Lukasz

1

これは、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)です。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.