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.17s中的 shell 脚本相比,它在7.23s中运行。我尝试过使用更大的文件,并且 Python 似乎总是获胜。我对上面的 senario 有几个问题:

  1. 鉴于 shell 命令是用 C 编写的,为什么 Python 脚本更快?我确实意识到 shell 脚本可能不是最佳脚本。
  2. 如何改进 shell 脚本?
  3. 我可以改进 Python 脚本吗?

需要明确的是,我不是将 Python 与 shell 脚本进行比较。我不是想开始一场激烈的战争,也不需要任何其他语言的答案来比较自己更快。使用 UNIX 管道小命令来完成任务的哲学,我如何使 shell 脚本更快?

4

6 回答 6

7

这里很重要的一点可能是进程间 I/O。Python 脚本的所有数据都在内存中,因此在处理数据时不会发生 I/O。

另请注意,Python 本身并不慢。Python 中的大多数功能都是用 C 实现的。

shell 脚本必须启动 5 个进程,每个进程必须读取整个文本stdin并将整个文本写入stdout四次。

可能有一种方法可以使 Python 脚本更快一些:您可以将整个文本读入一个字符串,然后删除所有标点符号,拆分单词然后计算它们:

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

这将避免几个嵌套循环的开销。

至于shell脚本:你应该尽量减少进程数。这三个tr进程可能会被替换为一次调用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

bash 脚本

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

一个 bash 解决方案

#!/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 回答