Đây có lẽ là một trong những thách thức mã hóa cổ điển gây được tiếng vang vào năm 1986, khi chuyên mục Jon Bentley yêu cầu Donald Knuth viết một chương trình sẽ tìm thấy k từ thường xuyên nhất trong một tệp. Knuth đã triển khai một giải pháp nhanh bằng cách sử dụng hàm băm trong một chương trình dài 8 trang để minh họa cho kỹ thuật lập trình biết chữ của mình. Douglas McIlroy của Bell Labs đã chỉ trích giải pháp của Knuth là thậm chí không thể xử lý toàn bộ Kinh thánh và trả lời bằng một bản tóm tắt, điều đó không nhanh chóng, 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
Năm 1987, một bài báo tiếp theo đã được xuất bản với một giải pháp khác, lần này là bởi một giáo sư Princeton. Nhưng nó thậm chí không thể trả lại kết quả cho một Kinh thánh!
Mô tả vấn đề
Mô tả vấn đề gốc:
Cho một tệp văn bản và một số nguyên k, bạn phải in k từ phổ biến nhất trong tệp (và số lần xuất hiện của chúng) với tần suất giảm.
Làm rõ vấn đề bổ sung:
- Knuth định nghĩa một từ là một chuỗi các chữ cái Latinh:
[A-Za-z]+
- tất cả các nhân vật khác bị bỏ qua
- chữ hoa và chữ thường được coi là tương đương (
WoRd
==word
) - không giới hạn kích thước tập tin cũng như độ dài từ
- khoảng cách giữa các từ liên tiếp có thể lớn tùy ý
- chương trình nhanh nhất là chương trình sử dụng tổng thời gian CPU ít nhất (đa luồng có thể sẽ không giúp ích)
Các trường hợp thử nghiệm mẫu
Bài kiểm tra 1: Ulysses của James Joyce đã ghép nối 64 lần (tệp 96 MB).
- Tải xuống Ulysses từ Project Gutenberg:
wget http://www.gutenberg.org/files/4300/4300-0.txt
- Nối nó 64 lần:
for i in {1..64}; do cat 4300-0.txt >> ulysses64; done
- Từ thường gặp nhất là những người nổi tiếng với 968832 lần xuất hiện.
Kiểm tra 2: Văn bản ngẫu nhiên được tạo đặc biệt giganovel
(khoảng 1 GB).
- Kịch bản trình tạo Python 3 tại đây .
- Văn bản chứa 148391 từ riêng biệt xuất hiện tương tự như ngôn ngữ tự nhiên.
- Những từ thường gặp nhất: sự xuất hiện của 11 tuổi (11309 lần xuất hiện) và sự xuất hiện của tôi (11290 lần xuất hiện).
Kiểm tra tổng quát: các từ lớn tùy ý với các khoảng trống lớn tùy ý.
Tham khảo thực hiện
Sau khi xem xét Rosetta Code cho vấn đề này và nhận ra rằng nhiều triển khai rất chậm (chậm hơn so với kịch bản shell!), Tôi đã thử nghiệm một vài triển khai tốt ở đây . Dưới đây là hiệu suất cho ulysses64
cùng với độ phức tạp thời gian:
ulysses64 Time complexity
C++ (prefix trie + heap) 4.145 O((N + k) log k)
Python (Counter) 10.547 O(N + k log Q)
AWK + sort 20.606 O(N + Q log Q)
McIlroy (tr + sort + uniq) 43.554 O(N log N)
Bạn có thể đánh bại nó?
Kiểm tra
Hiệu suất sẽ được đánh giá bằng MacBook Pro 13 "2017 với time
lệnh Unix tiêu chuẩn (thời gian" người dùng "). Nếu có thể, vui lòng sử dụng trình biên dịch hiện đại (ví dụ: sử dụng phiên bản Haskell mới nhất, không phải phiên bản kế thừa).
Thứ hạng cho đến nay
Thời gian, bao gồm các chương trình tham khảo:
k=10 k=100K
ulysses64 giganovel giganovel
C (trie + bins) by Moogie 0.704 9.568 9.459
C (trie + list) by Moogie 0.767 6.051 82.306
C (trie + sorted list) by Moogie 0.804 7.076 x
Rust (trie) by Anders Kaseorg 0.842 6.932 7.503
J by miles 1.273 22.365 22.637
C# (trie) by recursive 3.722 25.378 24.771
C++ (trie + heap) 4.145 42.631 72.138
APL (Dyalog Unicode) by Adám 7.680 x x
Python (dict) by movatica 9.387 99.118 100.859
Python (Counter) 10.547 102.822 103.930
Ruby (tally) by daniero 15.139 171.095 171.551
AWK + sort 20.606 213.366 222.782
McIlroy (tr + sort + uniq) 43.554 715.602 750.420
Xếp hạng tích lũy * (%, điểm tốt nhất có thể - 300):
# Program Score Generality
1 Rust (trie) by Anders Kaseorg 334 Yes
2 C (trie + bins) by Moogie 384 x
3 J by miles 852 Yes
4 C# (trie) by recursive 1278 x
5 C (trie + list) by Moogie 1306 x
6 C++ (trie + heap) 2255 x
7 Python (dict) by movatica 4316 Yes
8 Python (Counter) 4583 Yes
9 Ruby (tally) by daniero 7264 Yes
10 AWK + sort 9422 Yes
11 McIlroy (tr + sort + uniq) 28014 Yes
* Tổng hiệu suất thời gian liên quan đến các chương trình tốt nhất trong mỗi ba bài kiểm tra.
Chương trình tốt nhất: tại đây .