Подтвердить что ты не робот

Возможно ли сделать эту оболочку script быстрее?

У меня есть задача создать script, который принимает огромный текстовый файл в качестве ввода. Затем ему нужно найти все слова и количество вхождений и создать новый файл с каждой строкой, отображающей уникальное слово и его появление.

В качестве примера возьмите файл с этим контентом:

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
...

Для этого я написал script, используя 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 script:

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')

Когда я выполнил script, я получил:

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

Как видите, он работает в 7.23s по сравнению с оболочкой script, которая работает в 16.17s. Я пытался с большими файлами, и всегда Python, кажется, торжествует. У меня есть несколько вопросов к senario выше:

  • Почему Python script быстрее, если команды оболочки написаны на C? Я понимаю, что оболочка script может быть не оптимальной.
  • Как я могу улучшить оболочку script?
  • Могу ли я улучшить Python script?

Чтобы быть ясным, я не сравниваю Python с командами оболочки. Я не пытаюсь начать пламенную войну или не нуждаюсь в ответах на каком-либо другом языке, сравнивая себя быстрее. Используя философию UNIX для создания небольших команд для выполнения задачи, как сделать оболочку script быстрее?

4b9b3361

Ответ 1

Важным моментом здесь является, вероятно, межоперационный ввод-вывод. Python script имеет все данные в памяти, поэтому никаких операций ввода-вывода не происходит во время обработки данных.

Также обратите внимание, что Python не медленный как таковой. Большинство функций в Python реализовано на C.

В оболочке script должно быть запущено 5 процессов, и каждый из них должен прочитать весь текст из stdin и записать весь текст в stdout четыре раза.

Возможно, есть способ сделать Python script немного быстрее: вы можете прочитать весь текст в одну строку, затем удалить все знаки препинания, разделить слова и затем посчитать их:

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

Это позволит избежать накладных расходов нескольких вложенных циклов.

Что касается оболочки script: вы должны попытаться уменьшить количество процессов. Три процесса tr, вероятно, могут быть заменены одним вызовом на sed.

Ответ 2

Это не вопрос одного языка и другого. Ваш подход отличается.

В Python вы увеличиваете счетчик для каждого слова, когда вы его встретите, а затем итерируете свой счетчик для вывода результата. Это будет O (n).

В bash вы помещаете все свои слова индивидуально в длинный кортеж, сортируя кортеж, затем подсчитывая экземпляры. Это скорее всего будет O (nlogn) для сортировки.

Ответ 3

Вы можете улучшить свой bash script:

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

Но короткий и правильный ответ на ваш вопрос: потому что вы используете совершенно разные алгоритмы.

Ответ 4

Вы можете попробовать следующее:

Учитывая входной файл для Input.txt

Bash script

cat Input.txt | tr [:space:] '\n' | grep -v "^\s*$" | sort | uniq -c | sort -bnr | tr [:lower:] [:upper:]

Ответ 5

Один из способов: 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

Ответ 6

a bash solution

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