знайти 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 тощо, де я зберігаю (слово, numberOfOccurences) у словнику і сортую значення, або я міг би використовувати MapReduce, але оптимізую для натискань клавіш.

Чи є помилкові позитиви? Чи є кращий спосіб?


чому б ви поставили -10 в кінці? : P
anu

Відповіді:


47

Це, найчастіше, найпоширеніший спосіб пошуку "N найпоширеніших речей", за винятком того, що ви пропущені sort, і у вас виграшно cat:

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

Якщо ви не ставите sortперед цим, uniq -c ви, ймовірно, отримаєте багато помилкових однотонних слів. uniqтільки унікальні прогони ліній, а не загальна унікальність.

EDIT: Я забув трюк, "стоп слова". Якщо ви дивитесь англійський текст (вибачте, тут є одномовна північноамериканська мова), такі слова, як "з", "і", "," майже завжди займають перші два-три місця. Ви, мабуть, хочете їх усунути. У дистрибутиві GNU Groff є файл, названий eignв ньому, який містить досить пристойний список слів зупинки. Мій дистриб'ютор Arch має /usr/share/groff/current/eign, але я думаю, що я бачив /usr/share/dict/eignі /usr/dict/eignв старих Unixes.

Ви можете використовувати слова стоп, як це:

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

Я здогадуюсь, що більшості людських мов потрібні подібні «слова зупинки», вилучені із значущих підрахунків частот слів, але я не знаю, де запропонувати отримати списки списків інших зупинок інших мов.

EDIT: fgrep повинен використовувати -wкоманду, яка дозволяє зіставити цілі слова. Це дозволяє уникнути помилкових позитивних слів на словах, які містять лише короткі зупинки, наприклад "a" або "i".


2
Чи catдодає якісь значні показники ефективності? Мені подобається синтаксис труби. Що робить * у '[\ n *]'?
Лукаш Мадон

1
Якщо вам подобається "cat test.txt", то будь-яким чином використовуйте його. Я десь прочитав статтю, де Денніс Річі говорить, що синтаксис "кішка щось | щось інше" є більш широко використовуваним, і що синтаксис "щось" був помилкою, оскільки це єдина мета.
Брюс Едігер

Що робити, якщо я хочу знайти найпоширеніше ім'я каталогу у findвихідному? Тобто, розділіть слова на /символи, а не пробіли тощо.
erb

1
@erb - ти, мабуть, зробиш щось на кшталт:find somewhere optoins | tr '/' '\n' | sort | uniq -c | sort -k1.1nr | head -10
Брюс Едігер

1
@erb - задайте це як питання, а не в коментарі. У вас буде більше місця для обрамлення вашого питання, щоб отримати потрібну відповідь. Наведіть приклад введення та бажаний вихід. Ви можете отримати кілька балів репутації, щоб задати гарне запитання, і я отримаю бали за те, що краще відповісти, ніж можу в коментарі.
Брюс Едігер


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-палатка 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.
Андрій Макуха

@AndriyMakukha Вузьким місцем є те, що рядки пов'язані списками символів у Haskell. Ми могли отримати швидкість, схожу на С, перейшовши на Textабо ByteStringзамість цього, що так само просто, як імпортувати його кваліфіковано та префіксувати функції за допомогою класифікатора.
BlackCap

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));'
Лукаш Мадон

1

Це класична проблема, яка отримала певний резонанс у 1986 році, коли Дональд Кнут реалізував швидке рішення з хеш-спробами в програмі, що триває 8 сторінок, щоб проілюструвати його грамотну техніку програмування, в той час як Дуг Макілрой, хрещений батько Unix труб, відповів це було не так швидко, але робота виконана:

tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed 10q

Звичайно, рішення Макілроя має складність у часі O (N log N), де N - загальна кількість слів. Є набагато швидші рішення. Наприклад:

Ось реалізація C ++ із верхньою межею часової складності O ((N + k) log k), як правило - майже лінійна.

Нижче представлена ​​швидка реалізація Python, використовуючи хеш-словники та купу з часовою складністю O (N + k log Q), де 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)

Порівняння процесорного часу (у секундах):

                                     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 МБ), біблія256 - 256 разів відповідно (1,1 ГБ).
  • Нелінійне уповільнення сценаріїв Python викликано тим, що він обробляє файли повністю в пам'яті, тому накладні витрати стають більшими для величезних файлів.
  • Якби був інструмент Unix, який міг би сконструювати купу і вибрати n елементів з верху купи, рішення AWK могло б досягти майже лінійної часової складності, в той час як це O (N + Q log Q).
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.