tìm n từ thường xuyên nhất trong một tập tin


34

Tôi muốn tìm, nói, 10 từ phổ biến nhất trong một tệp văn bản. Đầu tiên, giải pháp nên được tối ưu hóa cho tổ hợp phím (nói cách khác - thời gian của tôi). Thứ hai, về hiệu suất. Đây là những gì tôi có cho đến nay để có được top 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

Tôi có thể tạo một chương trình java, python, v.v. nơi tôi lưu trữ (word, numberOfOccurences) trong từ điển và sắp xếp giá trị hoặc tôi có thể sử dụng MapReduce, nhưng tôi tối ưu hóa cho tổ hợp phím.

Có bất kỳ dương tính giả? Có cách nào tốt hơn?


Tại sao bạn lại đặt một -10 ở cuối? : P
anu

Câu trả lời:


47

Đó là cách phổ biến nhất để tìm "N những điều phổ biến nhất", ngoại trừ bạn đang thiếu một sort, và bạn có một sự vô cớ cat:

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

Nếu bạn không đặt sorttrước, uniq -c có lẽ bạn sẽ nhận được rất nhiều từ đơn lẻ. uniqchỉ thực hiện các dòng duy nhất, không phải là duy nhất tổng thể.

EDIT: Tôi quên một mẹo, "dừng từ". Nếu bạn đang xem văn bản tiếng Anh (xin lỗi, Bắc Mỹ đơn ngữ ở đây), các từ như "của", "và", "hầu như luôn luôn chiếm hai hoặc ba vị trí hàng đầu. Bạn có thể muốn loại bỏ chúng. Bản phân phối GNU Groff có một tệp có tên eigntrong đó chứa một danh sách khá nhiều từ dừng. Bản phân phối Arch của tôi có /usr/share/groff/current/eign, nhưng tôi nghĩ rằng tôi cũng đã thấy /usr/share/dict/eignhoặc /usr/dict/eigntrong các Unix cũ.

Bạn có thể sử dụng các từ dừng như thế này:

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

Tôi đoán là hầu hết các ngôn ngữ của con người cần loại bỏ "từ dừng" tương tự khỏi số lượng tần số từ có ý nghĩa, nhưng tôi không biết nên đề xuất các ngôn ngữ khác dừng danh sách từ nào.

EDIT: fgrep nên sử dụng -wlệnh, cho phép khớp toàn bộ từ. Điều này tránh được các thông báo sai về các từ chỉ chứa các tác phẩm dừng ngắn, như "a" hoặc "i".


2
catthêm một số chi phí hiệu suất đáng kể? Tôi thích cú pháp ống. * Trong '[\ n *]' làm gì?
Lukasz Madon

1
Nếu bạn thích "cat test.txt", thì bằng mọi cách hãy sử dụng nó. Tôi đã đọc một bài báo ở đâu đó nơi Dennis Ritchie nói rằng cú pháp "cat Something | Somethingelse" được sử dụng rộng rãi hơn và cú pháp '<cái gì đó' là một lỗi, vì đó là mục đích duy nhất.
Bruce Ediger

Nếu tôi muốn tìm tên thư mục phổ biến nhất trong một findđầu ra thì sao? Đó là, chia các từ trên /thay vì các ký tự khoảng trắng và tương tự.
erb

1
@erb - bạn có thể sẽ làm một cái gì đó như:find somewhere optoins | tr '/' '\n' | sort | uniq -c | sort -k1.1nr | head -10
Bruce Ediger

1
@erb - Hỏi như một câu hỏi, không phải trong một bình luận. Bạn sẽ có nhiều chỗ hơn để đóng khung câu hỏi của bạn, để có được câu trả lời bạn cần. Cho ví dụ đầu vào, và đầu ra mong muốn. Bạn có thể nhận được một số điểm danh tiếng khi đặt câu hỏi hay và tôi sẽ nhận được điểm khi đưa ra câu trả lời tốt hơn tôi có thể nhận xét.
Bruce Ediger

8

Điều này hoạt động tốt hơn với utf-8:

$ sed -e 's/\s/\n/g' < test.txt | sort | uniq -c | sort -nr | head  -10

7

Hãy sử dụng AWK!

Hàm này liệt kê tần suất của mỗi từ xuất hiện trong tệp được cung cấp theo thứ tự Giảm dần:

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
}

Bạn có thể gọi nó trên tập tin của bạn như thế này:

$ cat your_file.txt | wordfrequency

và cho 10 từ hàng đầu:

$ cat your_file.txt | wordfrequency | head -10

Nguồn: AWK-phường Ruby


4

Hãy sử dụng Haskell!

Điều này đang biến thành một cuộc chiến ngôn ngữ, phải không?

import Data.List
import Data.Ord

main = interact $ (=<<) (\x -> show (length x) ++ " - " ++ head x ++ "\n")
                . sortBy (flip $ comparing length)
                . group . sort
                . words

Sử dụng:

cat input | wordfreq

Cách khác:

cat input | wordfreq | head -10

phiên bản sửa đổi bỏ qua trường hợp: pastebin.com/57T5B6BY
Axel Latvala

Hoạt động chậm hơn nhiều so với cổ điển sort | uniq -c | sort -nr.
Andriy Makukha

@AndriyMakukha Nút cổ chai là các chuỗi được liên kết danh sách các ký tự trong Haskell. Chúng ta có thể có được tốc độ như C bằng cách chuyển sang Texthoặc ByteStringthay vào đó, đơn giản như việc nhập nó đủ điều kiện và tiền tố các chức năng với vòng loại.
BlackCap

pastebin.com/QtJjQwT9 phiên bản nhanh hơn đáng kể, được viết để dễ đọc
BlackCap

3

Một cái gì đó như thế này sẽ hoạt động bằng cách sử dụng python thường có sẵn:

cat slowest-names.log | python -c 'import collections, sys; print collections.Counter(sys.stdin);'

Điều này giả định từ trên mỗi dòng. Nếu có nhiều hơn, việc chia tách cũng dễ dàng.


python3 và đầu ra đẹp hơncat README.md | python -c 'import collections, sys, pprint; pprint.pprint(collections.Counter(sys.stdin));'
Lukasz Madon

1

Đây là một vấn đề kinh điển gây được tiếng vang vào năm 1986, khi Donald Knuth thực hiện một giải pháp nhanh với hàm băm trong một chương trình dài 8 trang để minh họa kỹ thuật lập trình biết chữ của mình, trong khi Doug McIlroy, cha đỡ đầu của các ống Unix, đã trả lời một lớp lót, điều đó không nhanh như vậy, nhưng đã hoàn thành công việc:

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

Tất nhiên, giải pháp của McIlroy có độ phức tạp thời gian O (N log N), trong đó N là tổng số từ. Có nhiều giải pháp nhanh hơn. Ví dụ:

Dưới đây là một triển khai C ++ với độ phức tạp thời gian giới hạn trên O ((N + k) log k), thường - gần như tuyến tính.

Dưới đây là một triển khai Python nhanh bằng cách sử dụng từ điển băm và heap với độ phức tạp thời gian O (N + k log Q), trong đó Q là một số từ duy nhất:

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)

So sánh thời gian CPU (tính bằng giây):

                                     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

Ghi chú:

  • bible32 là Kinh thánh được nối với chính nó 32 lần (135 MB), kinh thánh256 - 256 lần tương ứng (1,1 GB).
  • Sự chậm lại phi tuyến tính của các tập lệnh Python được gây ra hoàn toàn bởi thực tế là nó xử lý các tệp hoàn toàn trong bộ nhớ, do đó, các chi phí ngày càng lớn hơn đối với các tệp lớn.
  • Nếu có một công cụ Unix có thể xây dựng một heap và chọn n phần tử từ đầu heap, giải pháp AWK có thể đạt được độ phức tạp thời gian gần tuyến tính, trong khi hiện tại nó là O (N + Q log Q).
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.