1

ディレクトリDには、.eml形式の数千通の電子メールが含まれています。一部の電子メールはプレーンテキストであり、他の電子メールはOutlookから送信され、他の電子メールにはASCIIヘッダーとHTML/MIMEコンテンツなどがあります。Dディレクトリの下のファイルで検索する興味深い単語のリスト(つまり、red \ nblue \ ngreen \ n ...)を含む辞書ファイルFが存在します。Dディレクトリには多数のサブフォルダがありますが、上記の.emlファイル以外のファイルはありません。繰り返し発生する上位の単語のリストは、次の仕様で作成する必要があります。

  • 興味深い単語ごとに、それが何回発生し、どこで発生するかに関する情報を提供する必要があります。ファイル内で複数回発生する場合は、そのファイルについて複数回報告する必要があります。発生の報告とは、整数のタプル(L、P)を報告することを意味します。ここで、Lは電子メールソースの先頭からの行番号であり、Pはその行内での発生の開始位置です。

これにより、さまざまな出現箇所を参照するためのインデックスと、最も頻繁に出現する興味深い単語の要約の両方が作成されます。

上記の情報が含まれている場合、出力は単一の出力ファイルにある必要があり、形式は厳密には定義されていません。興味深い単語、各興味深い単語の出現回数、および出現場所-> file / line/start-position。

これは宿題ではありませんが、かなり大きなデータセットを作成したい実際のテキスト分析です。私が抱えている課題は、効率的にフィルタリングするための適切なツールを選択することです。反復アプローチであるwords/emails / etcのデカルト積は遅すぎるため、各ファイルの各行に複数の単語フィルタリングを組み合わせることが望ましいでしょう。

興味深い単語のリストw1|w2 | w3 | ...から代替の正規表現を作成し、それをコンパイルして各電子メールの各行で実行することを試みましたが、特に複数の単語をチェックする必要がある場合は、まだ時間がかかります。 1行以内のオカレンス。

例:

EメールEには、次のテキストを含む行があります。

^...何とか...赤いリンゴ...青いブルーベリー...赤、白、青の旗。$ \ n

正規表現はred(2)とblue(2)を正しく報告しますが、興味深い単語の実際の非常に大きな辞書を使用すると遅くなります。

私が試した別のアプローチは次のとおりです。

Sqliteデータベースを使用して、解析時にトークンをダンプします。これには、各エントリの(列、位置)情報が含まれ、最後に出力をクエリするだけです。バッチ挿入は、適切なメモリ内バッファを使用すると非常に役立ちますが、複雑さが増します。

そもそもトークン/解析が正しいことかどうかわからないため、データの並列化についてはまだ実験していません。たぶん、手紙の木がより適しているでしょうか?

優先順に、のソリューションに興味があります。

  • Bash / GNU CLIツール(特に、CLIのみの実行のためにGNU'parallel'を介して並列化可能なもの)
  • Python(NLP?)
  • C / C ++

残念ながら、私はそれを理解していないので、Perlはありません。

4

3 に答える 3

2

emlからテキストへのコンバーターを作成/検索できると思います。次に、これはあなたが望むものにかなり近いです:

find -type f | parallel --tag 'eml-to-text {} | grep -o -n -b -f /tmp/list_of_interesting_words'

出力は、希望どおりに100%フォーマットされていません。

ファイル名\t行番号:バイト番号(ファイルの先頭から):ワード

興味深い単語がたくさんある場合は、「-f」のgrep起動に時間がかかるため、maildirの解凍バージョンを作成できる場合は、並列起動の回数をgrep減らすことができます。

find . -type f | parallel 'eml-to-text {} >/tmp/unpacked/{#}'
find /tmp/unpacked -type f | parallel -X grep -H -o -n -b -f /tmp/list_of_interesting_words

の時間計算量はgrep -f線形よりも悪いため、/ tmp/list_of_interesting_wordsをより小さなブロックに分割することをお勧めします。

cat /tmp/list_of_interesting_words | parallel --pipe --block 10k --files > /tmp/blocks_of_words

次に、ブロックとファイルを並行して処理します。

find /tmp/unpacked -type f | parallel -j1 -I ,, parallel --arg-file-sep // -X grep -H -o -n -b -f ,, {} // - :::: /tmp/blocks_of_words

この出力の形式は次のとおりです。

ファイル名:行番号:バイト番号(ファイルの先頭から):ワード

wordファイル名の代わりにグループ化するには、結果を並べ替えにパイプします。

... | sort -k4 -t: > index.by.word

頻度を数えるには:

... | sort -k4 -t: | tee index.by.word | awk 'FS=":" {print $4}' | uniq -c

幸いなことに、これはかなり高速であるはずです。Pythonを使用して同じ速度を達成できるとは思えません。

編集:

grep -Fは起動がはるかに高速であり、grepには-wが必要になります(したがって、「gram」という単語は「diagrams」と一致しません)。これにより、一時ファイルも回避され、おそらくかなり高速になります。

find . -type f | parallel --tag 'eml-to-text {} | grep -F -w -o -n -b -f /tmp/list_of_interesting_words' | sort -k3 -t: | tee index.by.word | awk 'FS=":" {print $3}' | uniq -c
于 2012-06-11T23:15:04.650 に答える
2

いくつかの意見:

  • 「すべての電子メールに対して、正規表現検索とdo_something();を実行する」という方針に沿って何かをしたくはありません。ほとんどのメールの長さは興味深い単語のリストよりも短いと想像できるので、各メールを個別に処理して必要な情報を抽出しようと思います。
  • 特殊な文字列データ構造(文字列トライ三分探索木など)を構築して、単語が興味深いかどうかをすばやく検索します。単語の三分探索木を構築すると、単語をすばやく検索できるので、良い経験ができます。
  • その場合、アルゴリズムは次のようになります。

(もちろん擬似コードで)

result <- empty list
for each email e:
    for each word w:
        if is_interesting_word(w, string_data_structure):
            add (filename, line_number, start_position, word) to results
  • この問題は、 MapReduceHadoopなど)などの手法との並列化に非常に適しています。各電子メールは他の電子メールとは独立して処理でき、情報を共有する必要はありません。文字列データ構造は、電子メールを処理する前に計算できます。マップステップでは、電子メールから必要な情報を抽出し、リデュースステップでは、各電子メールから計算された値を単一の出力ファイルにマージします。

必要な処理の量を減らします。正規表現や高度な解析は必要ありません。電子メールの各文字/行に目を通し、現在地(行番号、位置など)を追跡するだけです。最後のステップとして、コードのプロファイルを作成し、問題のある場所を最適化します:)

于 2012-06-11T19:41:42.533 に答える
0

Python:

list = ['a', 'bunch', 'of', 'interesting', 'words']
linepos = 0

with open("file") as f:
    for line in f:
        linepos += 1
        wordpos = 0
        for word in line.split():
            wordpos += 1
            if word in list:
                print "%s found at line %s, word %s" % (word, linepos, wordpos)
于 2012-06-11T19:57:23.227 に答える