13

巨大なテキスト ファイルを入力として受け取るスクリプトを作成するタスクがあります。次に、すべての単語と出現回数を検索し、各行に一意の単語とその出現回数を表示する新しいファイルを作成する必要があります。

例として、次の内容のファイルを取り上げます。

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.   
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt 
mollit anim id est laborum.

次のようなファイルを作成する必要があります。

1 AD
1 ADIPISICING
1 ALIQUA
...
1 ALIQUIP
1 DO
2 DOLOR
2 DOLORE
...

trこのために、 、 、sortおよびを使用してスクリプトを作成しましたuniq

#!/bin/sh
INPUT=$1
OUTPUT=$2
if [ -a $INPUT ]
then
    tr '[:space:][\-_?!.;\:]' '\n' < $INPUT | 
        tr -d '[:punct:][:special:][:digit:]' |
        tr '[:lower:]' '[:upper:]' |
        sort |
        uniq -c > $OUTPUT
fi   

これが行うことは、区切り文字としてスペースで単語を分割することです。単語に含まれている場合は、-_?!.;:もう一度単語に分割します。句読点、特殊文字、数字を削除し、文字列全体を大文字に変換します。これが完了したら、それを並べ替えて渡し、必要なuniq形式にします。

ここで、聖書を txt 形式でダウンロードし、それを入力として使用しました。私が得たこれのタイミング:

scripts|$ time ./text-to-word.sh text.txt b     
./text-to-word.sh text.txt b  16.17s user 0.09s system 102% cpu 15.934 total

私はPythonスクリプトで同じことをしました:

import re
from collections import Counter
from itertools import chain
import sys

file = open(sys.argv[1])

c = Counter()

for line in file.readlines():
    c.update([re.sub('[^a-zA-Z]', '', l).upper()
            for l in chain(*[re.split('[-_?!.;:]', word)
                    for word in line.split()])])

file2 = open('output.txt', 'w')
for key in sorted(c):
    file2.write(key + ' ' + str(c[key]) + '\n')

スクリプトを実行すると、次のようになりました。

scripts|$ time python text-to-word.py text.txt
python text-to-word.py text.txt  7.23s user 0.04s system 97% cpu 7.456 total

ご覧のとおり、16.17 秒で実行されたシェル スクリプトと比較して、7.23 秒で実行されました。私はより大きなファイルで試してみましたが、常にPythonが勝利しているようです。上記のシナリオについていくつか質問があります。

  1. シェル コマンドが C で記述されているのに、Python スクリプトの方が速いのはなぜですか? シェルスクリプトが最適なものではない可能性があることは認識しています。
  2. シェルスクリプトを改善するにはどうすればよいですか?
  3. Python スクリプトを改善できますか?

明確にするために、私は Python をシェル スクリプトと比較していません。私はフレーム戦争を始めようとしているわけではありませんし、他の言語で自分自身をより速く比較する必要もありません。小さなコマンドをパイプしてタスクを実行するという UNIX の哲学を使用して、シェル スクリプトを高速化するにはどうすればよいでしょうか?

4

6 に答える 6

7

ここで重要な点は、おそらくプロセス間 I/O です。Python スクリプトではすべてのデータがメモリ内にあるため、データの処理中に I/O は発生しません。

また、Python 自体は遅くないことに注意してください。Python のほとんどの機能は C で実装されています。

シェル スクリプトは 5 つのプロセスを開始する必要があり、それぞれのプロセスでテキスト全体の読み取りstdinと書き込みをstdout4 回行う必要があります。

Python スクリプトを少し速くする方法があるかもしれません: テキスト全体を 1 つの文字列に読み込んでから、すべての句読点を削除し、単語を分割して、それらを数えることができます。

text = file.read()
text = re.sub(r'[.,:;-_]', '', text)
text = text.upper()
words = re.split(r'\\s+', text)
c = Counter()
c.update(words)

これにより、いくつかのネストされたループのオーバーヘッドが回避されます。

シェル スクリプトについては、プロセスの数を減らすようにしてください。3 つのtrプロセスは、おそらく 1 回の呼び出しで置き換えることができますsed

于 2012-08-16T13:26:28.047 に答える
3

ある言語と別の言語の問題ではありません。あなたのアプローチは異なります。

Python では、単語に遭遇するたびにカウンターをインクリメントし、カウンターを反復して出力を生成します。これは O(n) になります。

bash では、すべての単語を個別に長いタプルに入れ、タプルをソートしてからインスタンスを数えます。これは、ソートの O(nlogn) になる可能性が最も高いです。

于 2012-08-16T13:26:55.893 に答える
1

bash スクリプトを改善できます。

sed 's/[^a-zA-Z][^a-zA-Z]*/\'$'\n/g'  <$INPUT | sort -f -u >$OUTPUT

しかし、あなたの質問に対する簡潔で正しい答えは次のとおりです。まったく異なるアルゴリズムを使用しているためです。

于 2012-08-16T13:55:14.277 に答える
0

これを試すことができます:

入力ファイルをInput.txtとみなす

バッシュスクリプト

cat Input.txt | tr [:space:] '\n' | grep -v "^\s*$" | sort | uniq -c | sort -bnr | tr [:lower:] [:upper:]
于 2012-08-17T05:31:25.670 に答える
0

を使用した片道GNU awk

WHINY_USERS=1 awk '{ for (i=1; i<=NF; i++) { sub("[,.]","",$i); array[toupper($i)]++ } } END { for (j in array) print array[j], j }' file.txt

擬似コード/説明:

## WHINY_USERS=1 enables sorting by keys. A bit of a trick.
## Now loop through each word on each line, removing commas, full-stops,
## adding each word in uppercase to an array.
## Loop through the array printing vals and keys

YMMV

于 2012-08-17T06:38:34.113 に答える
0

バッシュソリューション

#!/bin/bash
IFS=' -_?!.;\:,'
while read -r line; do
  for word in $line; do
    word=${word//[^[:alpha:]]/}
    [ $word ] || continue
    word=$(tr '[:lower:]' '[:upper:]' <<<"$word")
    ((_w_$word++))
  done
done <"$INPUT"
IFS=' '
for wword in ${!_w_*}; do echo "${!wword} ${wword#_w_}"; done > $OUTPUT.v1

Perl ゴルフ ソリューション

perl -nle '$h{uc()}++for/(\w+)/g}{print"$h{$_} $_"for sort keys%h'  $INPUT > $OUTPUT.v2
于 2012-08-17T08:57:07.023 に答える