Thuật toán tìm 10 cụm từ tìm kiếm hàng đầu


115

Tôi hiện đang chuẩn bị cho một cuộc phỏng vấn, và nó khiến tôi nhớ lại một câu hỏi mà tôi đã từng được hỏi trong một cuộc phỏng vấn trước đây, giống như sau:

"Bạn đã được yêu cầu thiết kế một số phần mềm để liên tục hiển thị 10 cụm từ tìm kiếm hàng đầu trên Google. Bạn được cấp quyền truy cập vào nguồn cấp dữ liệu cung cấp luồng tìm kiếm theo thời gian thực vô tận hiện đang được tìm kiếm trên Google. Mô tả thuật toán và cấu trúc dữ liệu nào bạn sẽ sử dụng để triển khai điều này. Bạn phải thiết kế hai biến thể:

(i) Hiển thị 10 cụm từ tìm kiếm hàng đầu mọi thời đại (tức là kể từ khi bạn bắt đầu đọc nguồn cấp dữ liệu).

(ii) Chỉ hiển thị 10 cụm từ tìm kiếm hàng đầu trong tháng qua, được cập nhật hàng giờ.

Bạn có thể sử dụng một ước tính gần đúng để có được danh sách top 10, nhưng bạn phải biện minh cho lựa chọn của mình. "
Tôi đã đánh bom trong cuộc phỏng vấn này và thực sự vẫn không biết làm thế nào để thực hiện điều này.

Phần đầu tiên yêu cầu 10 mục thường xuyên nhất trong một dãy con liên tục phát triển của danh sách vô hạn. Tôi đã xem xét các thuật toán lựa chọn, nhưng không thể tìm thấy bất kỳ phiên bản trực tuyến nào để giải quyết vấn đề này.

Phần thứ hai sử dụng một danh sách hữu hạn, nhưng do số lượng lớn dữ liệu đang được xử lý, bạn không thể thực sự lưu trữ cả tháng các cụm từ tìm kiếm trong bộ nhớ và tính toán biểu đồ mỗi giờ.

Vấn đề trở nên khó khăn hơn do danh sách top 10 đang được cập nhật liên tục, vì vậy bằng cách nào đó bạn cần tính toán top 10 của mình qua cửa sổ trượt.

Có ý kiến ​​gì không?


11
@BlueRaja - Đây không phải là một câu hỏi phỏng vấn ngu ngốc, đó là một cách diễn giải tồi về phía OP. Nó không yêu cầu các mục thường xuyên nhất trong danh sách vô hạn, nó yêu cầu các mục thường xuyên nhất của một dãy con hữu hạn của danh sách vô hạn. Để tiếp tục tương tự của bạn,what is the most frequent item in the subsequence [2; 2; 3; 3; 3; 4; 4; 4; 4; 5; 5] of your sequence?
IVlad

3
@BlueRaja - Đó chắc chắn là một câu hỏi khó, nhưng tôi không hiểu tại sao nó lại ngu ngốc - nó có vẻ đại diện cho một vấn đề khá điển hình mà các công ty có bộ dữ liệu khổng lồ đang phải đối mặt. @IVlad - Đã sửa nó theo đề xuất của bạn, từ ngữ xấu về phía tôi!
del

Câu trả lời:


47

Chà, trông giống như một lượng dữ liệu khủng khiếp, với chi phí có lẽ quá cao để lưu trữ tất cả các tần số. Khi lượng dữ liệu quá lớn mà chúng ta không thể hy vọng có thể lưu trữ được hết, chúng ta nhập miền thuật toán dòng dữ liệu .

Cuốn sách hữu ích trong lĩnh vực này: Muthukrishnan - "Dòng dữ liệu: Thuật toán và ứng dụng"

Tham chiếu liên quan chặt chẽ đến vấn đề mà tôi đã chọn ở trên: Manku, Motwani - "Đếm tần suất gần đúng qua các luồng dữ liệu" [pdf]

Nhân tiện, Motwani, ở Stanford, (chỉnh sửa) là tác giả của cuốn sách rất quan trọng "Các thuật toán ngẫu nhiên" . Chương 11 của cuốn sách này đề cập đến vấn đề này . Chỉnh sửa: Xin lỗi, tài liệu tham khảo không hợp lệ, chương cụ thể đó thuộc một vấn đề khác. Sau khi kiểm tra, thay vào đó, tôi giới thiệu phần 5.1.2 của cuốn sách của Muthukrishnan , có sẵn trên mạng.

Heh, câu hỏi phỏng vấn hay.


2
+1 Nội dung rất thú vị, nên có một cách trên các trang web để gắn thẻ nội dung "để đọc". Cám ơn vì đã chia sẻ.
Ramadheer Singh

@Gollum: Tôi có một thư mục để đọc trong dấu trang của mình; bạn chỉ có thể làm điều đó. Tôi biết những liên kết đó đang được thêm vào của tôi :)
Cam

+1. Thuật toán phát trực tuyến chính xác là chủ đề ở đây và cuốn sách của Muthu (cuốn sách duy nhất được viết cho đến nay, AFAIK) rất tuyệt.
ShreevatsaR

1
+1. Liên quan: en.wikipedia.org/wiki/Online_algorithm . btw, Motwani đã chết gần đây, vì vậy có lẽ một tác giả là chính xác hơn.

Rất lạ. Tôi biết anh ấy từ cuốn sách, nhưng anh ấy chắc chắn phải nổi tiếng hơn vì điều này: "Motwani là một trong những đồng tác giả (với Larry Page và Sergey Brin, và Terry Winograd) của một bài báo ban đầu có ảnh hưởng về thuật toán PageRank, cơ sở cho các kỹ thuật tìm kiếm của Google. "( en.wikipedia.org/wiki/Rajeev_Motwani )
Dimitris Andreou

55

Tổng quan về ước tính tần suất

Có một số thuật toán nổi tiếng có thể cung cấp ước tính tần suất cho một luồng như vậy bằng cách sử dụng một lượng bộ nhớ cố định. Một là Thường xuyên, của Misra và Gries (1982). Từ danh sách n mục, nó tìm tất cả các mục xảy ra hơn n / k lần, sử dụng k - 1 bộ đếm. Đây là tổng quát của thuật toán Đa số của Boyer và Moore (Fischer-Salzberg, 1982), trong đó k là 2. LossyCounting của Manku và Motwani (2002) và SpaceSaving của Metwally (2005) có các yêu cầu về không gian tương tự, nhưng có thể cung cấp các ước tính chính xác hơn theo một số điều kiện.

Điều quan trọng cần nhớ là các thuật toán này chỉ có thể cung cấp các ước tính tần số. Cụ thể, ước tính Misra-Gries có thể đếm thấp tần suất thực tế bằng (n / k) mục.

Giả sử rằng bạn đã có một thuật toán có thể xác định một cách tích cực một mặt hàng chỉ khi nó xảy ra hơn 50% thời gian. Cung cấp cho thuật toán này một luồng gồm N mục riêng biệt, và sau đó thêm N - 1 bản sao khác của một mục, x , với tổng số là 2 - 1 mục. Nếu thuật toán cho bạn biết rằng x vượt quá 50% tổng số, nó phải nằm trong luồng đầu tiên; nếu không, x không có trong luồng ban đầu. Để thuật toán thực hiện xác định này, nó phải lưu trữ luồng ban đầu (hoặc một số tóm tắt tỷ lệ với độ dài của nó)! Vì vậy, chúng tôi có thể tự chứng minh rằng không gian được yêu cầu bởi một thuật toán "chính xác" như vậy sẽ là Ω ( N ).

Thay vào đó, các thuật toán tần suất được mô tả ở đây cung cấp một ước tính, xác định bất kỳ mục nào vượt quá ngưỡng, cùng với một số mục nằm dưới ngưỡng đó một biên độ nhất định. Ví dụ , thuật toán Đa số , sử dụng một bộ đếm duy nhất, sẽ luôn cho một kết quả; nếu bất kỳ mục nào vượt quá 50% luồng, mục đó sẽ được tìm thấy. Nhưng nó cũng có thể cung cấp cho bạn một mục chỉ xuất hiện một lần. Bạn sẽ không biết nếu không thực hiện lần thứ hai chuyển qua dữ liệu (sử dụng lại một bộ đếm duy nhất, nhưng chỉ tìm kiếm mục đó).

Thuật toán thường xuyên

Đây là một mô tả đơn giản về thuật toán thường xuyên của Misra-Gries . Demaine (2002) và những người khác đã tối ưu hóa thuật toán, nhưng điều này mang lại cho bạn ý chính.

Chỉ định phân số ngưỡng, 1 / k ; bất kỳ mục nào xuất hiện nhiều hơn n / k lần sẽ được tìm thấy. Tạo một bản đồ trống (như một cây đỏ-đen); các khóa sẽ là các cụm từ tìm kiếm và các giá trị sẽ là bộ đếm cho cụm từ đó.

  1. Xem từng mục trong luồng.
  2. Nếu thuật ngữ tồn tại trong bản đồ, hãy tăng bộ đếm liên quan.
  3. Ngược lại, nếu bản đồ có ít hơn k - 1 mục nhập, hãy thêm cụm từ vào bản đồ với bộ đếm là một.
  4. Tuy nhiên, nếu bản đồ đã có k - 1 mục nhập, hãy giảm bộ đếm trong mỗi mục nhập. Nếu bất kỳ bộ đếm nào về 0 trong quá trình này, hãy xóa bộ đếm đó khỏi bản đồ.

Lưu ý rằng bạn có thể xử lý một lượng dữ liệu vô hạn với một lượng lưu trữ cố định (chỉ bản đồ có kích thước cố định). Dung lượng lưu trữ cần thiết chỉ phụ thuộc vào ngưỡng quan tâm và kích thước của luồng không quan trọng.

Đếm tìm kiếm

Trong bối cảnh này, có lẽ bạn đệm một giờ tìm kiếm và thực hiện quá trình này trên dữ liệu của giờ đó. Nếu bạn có thể vượt qua lần thứ hai trên nhật ký tìm kiếm của giờ này, bạn có thể nhận được số lần xuất hiện chính xác của các "ứng cử viên" hàng đầu được xác định trong lần vượt qua đầu tiên. Hoặc, có thể bạn chỉ cần vượt qua một lượt và báo cáo tất cả các ứng cử viên, biết rằng bất kỳ mục nào cần có trong đó đều được đưa vào, và mọi thứ bổ sung chỉ là tiếng ồn sẽ biến mất trong giờ tới.

Bất kỳ ứng viên nào thực sự vượt quá ngưỡng quan tâm sẽ được lưu trữ dưới dạng tóm tắt. Giữ lại những bản tóm tắt này có giá trị trong một tháng, loại bỏ những bản tóm tắt cũ nhất mỗi giờ và bạn sẽ có một ước tính tốt về các cụm từ tìm kiếm phổ biến nhất.


Tôi tin rằng giải pháp này có thể hoạt động như một bộ lọc, giảm số lượng cụm từ tìm kiếm mà bạn quan tâm. Nếu một cụm từ xuất hiện trên bản đồ, hãy bắt đầu theo dõi số liệu thống kê thực tế của nó, ngay cả khi nó rơi ra khỏi bản đồ. Sau đó, bạn có thể bỏ qua lần thứ hai đối với dữ liệu và tạo ra một top 10 được sắp xếp từ số liệu thống kê hạn chế mà bạn thu thập được.
Dolph

Tôi thích cách trang nhã để cắt tỉa các cụm từ ít được tìm kiếm khỏi cây bằng cách giảm bớt các bộ đếm. Nhưng một khi bản đồ đã "đầy", điều đó sẽ không yêu cầu bước giảm dần cho mỗi cụm từ tìm kiếm mới xuất hiện? Và một khi điều này bắt đầu xảy ra, điều này sẽ không dẫn đến việc các cụm từ tìm kiếm mới hơn nhanh chóng bị xóa khỏi bản đồ trước khi chúng có cơ hội để bộ đếm của chúng tăng lên đủ?
del

1
@del - Hãy nhớ rằng thuật toán này là để xác định các thuật ngữ vượt quá tần suất ngưỡng được chỉ định, không nhất thiết để tìm các thuật ngữ phổ biến nhất. Nếu các thuật ngữ phổ biến nhất nằm dưới ngưỡng được chỉ định, chúng thường sẽ không được tìm thấy. Mối quan tâm của bạn về việc xóa các cụm từ mới hơn "quá nhanh" có thể liên quan đến trường hợp này. Một cách để xem xét điều này là có những "tín hiệu" thực sự về sự nổi tiếng, chúng sẽ nổi bật rõ rệt so với "tiếng ồn". Nhưng đôi khi, không có tín hiệu nào được tìm thấy, chỉ là tìm kiếm ngẫu nhiên tĩnh.
erickson

@erickson - Đúng vậy - điều tôi nhận được là giả định với thuật toán này là 10 từ hàng đầu được phân phối đồng đều trên cửa sổ đo lường. Nhưng miễn là bạn giữ cho cửa sổ đo đủ nhỏ (ví dụ 1 giờ), đây có thể là một giả định hợp lệ.
del

1
@erickson, trong khi phân phối đồng nhất không phải là một yêu cầu, tôi tự hỏi điều này sẽ hoạt động như thế nào trong một phân phối thực tế hơn (power-law, Zipf). Giả sử chúng ta có N từ riêng biệt và giữ nguyên cây màu đỏ-đen của khả năng K, hy vọng nó sẽ kết thúc với K số hạng thường xuyên nhất. Nếu tần suất tích lũy của các số hạng của (N - K) từ lớn hơn tần suất tích lũy của K từ thường xuyên nhất, thì cây cuối cùng được đảm bảo sẽ chứa rác. Bạn có đồng ý không?
Dimitris Andreou

19

Đây là một trong những dự án nghiên cứu mà tôi đang thực hiện. Yêu cầu gần như chính xác là của bạn và chúng tôi đã phát triển các thuật toán tốt để giải quyết vấn đề.

Đầu vào

Đầu vào là một dòng vô tận các từ hoặc cụm từ tiếng Anh (chúng tôi gọi chúng là tokens).

Đầu ra

  1. Xuất ra N mã thông báo hàng đầu mà chúng tôi đã thấy cho đến nay (từ tất cả các mã thông báo mà chúng tôi đã thấy!)
  2. Xuất ra N mã thông báo hàng đầu trong một cửa sổ lịch sử, chẳng hạn như ngày trước hoặc tuần trước.

Một ứng dụng của nghiên cứu này là tìm chủ đề nóng hoặc xu hướng của chủ đề trên Twitter hoặc Facebook. Chúng tôi có một trình thu thập thông tin thu thập dữ liệu trên trang web, tạo ra một luồng từ, sẽ đưa vào hệ thống. Sau đó, hệ thống sẽ xuất ra các từ hoặc cụm từ có tần suất cao nhất về tổng thể hoặc lịch sử. Hãy tưởng tượng trong vài tuần trước, cụm từ "World Cup" sẽ xuất hiện nhiều lần trên Twitter. “Bạch tuộc Paul” cũng vậy. :)

Chuỗi thành số nguyên

Hệ thống có một số nguyên ID cho mỗi từ. Mặc dù có thể có gần như vô hạn các từ trên Internet, nhưng sau khi tích lũy một lượng lớn các từ, khả năng tìm thấy từ mới ngày càng thấp. Chúng tôi đã tìm thấy 4 triệu từ khác nhau và chỉ định một ID duy nhất cho mỗi từ. Toàn bộ tập dữ liệu này có thể được tải vào bộ nhớ dưới dạng bảng băm, tiêu tốn khoảng 300MB bộ nhớ. (Chúng tôi đã triển khai bảng băm của riêng mình. Việc triển khai Java chiếm bộ nhớ lớn)

Sau đó, mỗi cụm từ có thể được xác định là một mảng các số nguyên.

Điều này rất quan trọng, bởi vì sắp xếp và so sánh trên số nguyên nhanh hơn nhiều so với trên chuỗi.

Lưu trữ dữ liệu

Hệ thống giữ dữ liệu lưu trữ cho mọi mã thông báo. Về cơ bản đó là các cặp (Token, Frequency). Tuy nhiên, bảng lưu trữ dữ liệu sẽ rất lớn nên chúng ta phải phân vùng bảng về mặt vật lý. Một khi lược đồ phân vùng dựa trên ngrams của mã thông báo. Nếu mã thông báo là một từ đơn lẻ, nó là 1gram. Nếu mã thông báo là cụm từ gồm hai từ, nó là 2gram. Và điều này tiếp tục. Khoảng 4gram, chúng tôi có 1 tỷ bản ghi, với kích thước bảng khoảng 60GB.

Xử lý luồng đến

Hệ thống sẽ hấp thụ các câu đến cho đến khi bộ nhớ được sử dụng hết (Ya, chúng ta cần một MemoryManager). Sau khi lấy N câu và lưu vào bộ nhớ, hệ thống tạm dừng và bắt đầu mã hóa từng câu thành các từ và cụm từ. Mỗi mã thông báo (từ hoặc cụm từ) được đếm.

Đối với các mã thông báo có tần suất cao, chúng luôn được lưu giữ trong bộ nhớ. Đối với các mã thông báo ít thường xuyên hơn, chúng được sắp xếp dựa trên ID (hãy nhớ rằng chúng tôi dịch Chuỗi thành một mảng số nguyên) và được tuần tự hóa thành tệp đĩa.

(Tuy nhiên, đối với vấn đề của bạn, vì bạn chỉ đếm các từ, nên bạn chỉ có thể đặt tất cả bản đồ tần suất từ ​​vào bộ nhớ. Một cấu trúc dữ liệu được thiết kế cẩn thận sẽ chỉ tiêu tốn bộ nhớ 300MB cho 4 triệu từ khác nhau. Một số gợi ý: sử dụng ký tự ASCII để đại diện cho Strings), và điều này có thể chấp nhận được.

Trong khi đó, sẽ có một quá trình khác được kích hoạt khi nó tìm thấy bất kỳ tệp đĩa nào do hệ thống tạo ra, sau đó bắt đầu hợp nhất nó. Vì tệp đĩa được sắp xếp nên việc hợp nhất sẽ diễn ra một quá trình tương tự như sắp xếp hợp nhất. Một số thiết kế cũng cần được quan tâm tại đây, vì chúng tôi muốn tránh quá nhiều lần tìm kiếm đĩa ngẫu nhiên. Ý tưởng là tránh đọc (quá trình hợp nhất) / ghi (đầu ra của hệ thống) cùng một lúc và để quá trình hợp nhất đọc tạo thành một đĩa trong khi ghi vào một đĩa khác. Điều này tương tự như thực hiện một khóa.

Cuối ngày

Vào cuối ngày, hệ thống sẽ có nhiều mã thông báo thường xuyên với tần suất được lưu trữ trong bộ nhớ và nhiều mã thông báo khác ít thường xuyên hơn được lưu trữ trong một số tệp đĩa (và mỗi tệp được sắp xếp).

Hệ thống chuyển bản đồ trong bộ nhớ vào một tệp đĩa (sắp xếp nó). Bây giờ, vấn đề trở thành hợp nhất một tập hợp các tệp đĩa đã được sắp xếp. Sử dụng quy trình tương tự, chúng tôi sẽ nhận được một tệp đĩa được sắp xếp ở cuối.

Sau đó, nhiệm vụ cuối cùng là hợp nhất tệp đĩa đã được sắp xếp vào cơ sở dữ liệu lưu trữ. Tùy thuộc vào kích thước của cơ sở dữ liệu lưu trữ, thuật toán hoạt động như dưới đây nếu nó đủ lớn:

   for each record in sorted disk file
        update archive database by increasing frequency
        if rowcount == 0 then put the record into a list
   end for

   for each record in the list of having rowcount == 0
        insert into archive database
   end for

Linh tính là sau một thời gian, số lần chèn sẽ ngày càng ít hơn. Ngày càng có nhiều hoạt động sẽ chỉ được cập nhật. Và việc cập nhật này sẽ không bị phạt theo chỉ số.

Hy vọng toàn bộ lời giải thích này sẽ hữu ích. :)


Tôi không hiểu. Người ta có thể thực hiện kiểu sắp xếp hoặc so sánh có ý nghĩa nào trong các ID số nguyên của các từ? Không phải là những con số tùy ý?
Dimitris Andreou,

Ngoài ra, đếm tần suất xuất hiện của các từ là ví dụ đầu tiên trong bài báo MapReduce của Google ( labs.google.com/papers/mapreduce.html ), giải quyết nó có thể mở rộng trong một vài dòng. Bạn thậm chí có thể di chuyển dữ liệu của bạn để google app angine và làm như vậy một MapReduce ( code.google.com/p/appengine-mapreduce )
Dimitris Andreou

@Dimitris Andreou: Sắp xếp theo số nguyên sẽ nhanh hơn trên chuỗi. Điều này là do so sánh hai số nguyên nhanh hơn so sánh hai chuỗi.
SiLent SoNG,

@Dimitris Andreou: bản đồ của Google là một cách tiếp cận phân tán tốt để giải quyết vấn đề này. Ah! Cảm ơn bạn đã cung cấp các liên kết. Ya, sẽ tốt cho chúng ta nếu chúng ta phân loại bằng nhiều máy. Cách tiếp cận tốt.
SiLent SoNG,

@Dimitris Andreou: Cho đến nay tôi chỉ đang xem xét phương pháp phân loại máy đơn lẻ. Thật là một ý tưởng hay để sắp xếp trong phân phối.
SiLent SoNG,

4

Bạn có thể sử dụng bảng băm kết hợp với cây tìm kiếm nhị phân . Triển khai một <search term, count>từ điển cho bạn biết mỗi cụm từ tìm kiếm đã được tìm kiếm bao nhiêu lần.

Rõ ràng việc lặp lại toàn bộ bảng băm mỗi giờ để có được top 10 là rất tệ. Nhưng đây là google mà chúng ta đang nói đến, vì vậy bạn có thể giả định rằng 10 người đứng đầu đều sẽ nhận được, giả sử hơn 10 000 lượt truy cập (mặc dù nó có thể là một con số lớn hơn nhiều). Vì vậy, mỗi khi số lượng cụm từ tìm kiếm vượt quá 10 000, hãy chèn cụm từ đó vào BST. Sau đó, mỗi giờ, bạn chỉ phải lấy 10 bài đầu tiên từ BST, sẽ chứa tương đối ít mục nhập.

Điều này giải quyết vấn đề của top 10 mọi thời đại.


Phần thực sự khó khăn là xử lý một thuật ngữ thay thế một thuật ngữ khác trong báo cáo hàng tháng (ví dụ: "tràn ngăn xếp" có thể có 50.000 lượt truy cập trong hai tháng qua, nhưng chỉ có 10.000 lượt truy cập vào tháng trước, trong khi "amazon" có thể có 40 000 trong hai tháng qua nhưng 30.000 cho tháng trước. Bạn muốn "amazon" xuất hiện trước "tràn ngăn xếp" trong báo cáo hàng tháng của mình). Để làm được điều này, tôi sẽ lưu trữ, đối với tất cả các cụm từ tìm kiếm chính (trên 10.000 lượt tìm kiếm mọi lúc), một danh sách 30 ngày cho bạn biết số lần cụm từ đó được tìm kiếm mỗi ngày. Danh sách sẽ hoạt động giống như hàng đợi FIFO: bạn xóa ngày đầu tiên và chèn một ngày mới mỗi ngày (hoặc mỗi giờ, nhưng sau đó bạn có thể cần lưu trữ nhiều thông tin hơn, có nghĩa là nhiều bộ nhớ / không gian hơn. Nếu bộ nhớ không phải là vấn đề, hãy làm nó, nếu không thì chuyển sang "ước lượng" đó

Đây có vẻ như là một khởi đầu tốt. Sau đó, bạn có thể lo lắng về việc cắt bỏ các cụm từ có hơn 10 000 lượt truy cập nhưng không có nhiều lượt truy cập trong một thời gian dài và những thứ như vậy.


3

trường hợp tôi)

Duy trì một bảng băm cho tất cả các công cụ tìm kiếm, cũng như một danh sách mười đầu được sắp xếp tách biệt với bảng băm. Bất cứ khi nào tìm kiếm xảy ra, hãy tăng mục thích hợp trong bảng băm và kiểm tra xem liệu bây giờ có nên chuyển mục đó bằng mục thứ 10 trong danh sách mười đầu hay không.

O (1) tra cứu danh sách mười đầu và chèn tối đa O (log (n)) vào bảng băm (giả sử xung đột được quản lý bởi cây nhị phân tự cân bằng).

trường hợp ii) Thay vì duy trì một bảng băm lớn và một danh sách nhỏ, chúng tôi duy trì một bảng băm và một danh sách được sắp xếp của tất cả các mục. Bất cứ khi nào một tìm kiếm được thực hiện, cụm từ đó sẽ được tăng lên trong bảng băm và trong danh sách đã sắp xếp, bạn có thể kiểm tra cụm từ đó để xem nó có nên chuyển với cụm từ sau nó hay không. Cây nhị phân tự cân bằng có thể hoạt động tốt cho việc này, vì chúng ta cũng cần có thể truy vấn nó nhanh chóng (sẽ nói thêm về điều này sau).

Ngoài ra, chúng tôi cũng duy trì danh sách 'giờ' dưới dạng danh sách FIFO (hàng đợi). Mỗi phần tử 'giờ' sẽ chứa danh sách tất cả các tìm kiếm được thực hiện trong giờ cụ thể đó. Vì vậy, ví dụ: danh sách giờ của chúng tôi có thể giống như sau:

Time: 0 hours
      -Search Terms:
          -free stuff: 56
          -funny pics: 321
          -stackoverflow: 1234
Time: 1 hour
      -Search Terms:
          -ebay: 12
          -funny pics: 1
          -stackoverflow: 522
          -BP sucks: 92

Sau đó, mỗi giờ: Nếu danh sách có ít nhất 720 giờ (đó là số giờ trong 30 ngày), hãy xem phần tử đầu tiên trong danh sách và đối với mỗi cụm từ tìm kiếm, hãy giảm phần tử đó trong bảng băm theo số lượng thích hợp . Sau đó, xóa phần tử giờ đầu tiên đó khỏi danh sách.

Vì vậy, giả sử chúng tôi đang ở giờ 721 và chúng tôi đã sẵn sàng xem giờ đầu tiên trong danh sách của chúng tôi (ở trên). Chúng tôi sẽ giảm 56 nội dung miễn phí trong bảng băm, các bức ảnh vui nhộn xuống 321, v.v. và sau đó sẽ xóa hoàn toàn giờ 0 khỏi danh sách vì chúng tôi sẽ không bao giờ cần nhìn lại nó nữa.

Lý do chúng tôi duy trì một danh sách được sắp xếp gồm tất cả các cụm từ cho phép truy vấn nhanh là vì mỗi giờ sau khi chúng tôi xem qua các cụm từ tìm kiếm từ 720 giờ trước, chúng tôi cần đảm bảo danh sách 10 cụm từ hàng đầu vẫn được sắp xếp. Vì vậy, khi chúng tôi giảm 'nội dung miễn phí' xuống 56 trong bảng băm chẳng hạn, chúng tôi sẽ kiểm tra xem nó hiện đang ở đâu trong danh sách. Bởi vì nó là một cây nhị phân tự cân bằng, tất cả những điều đó có thể được hoàn thành một cách độc đáo trong thời gian O (log (n)).


Chỉnh sửa: Độ chính xác hy sinh cho không gian ...

Cũng có thể hữu ích khi triển khai một danh sách lớn trong danh sách đầu tiên, cũng như trong danh sách thứ hai. Sau đó, chúng tôi có thể áp dụng tối ưu hóa không gian sau cho cả hai trường hợp: Chạy một công việc cron để xóa tất cả trừ x mục hàng đầu trong danh sách. Điều này sẽ giảm yêu cầu về dung lượng (và kết quả là làm cho các truy vấn trên danh sách nhanh hơn). Tất nhiên, nó sẽ dẫn đến một kết quả gần đúng, nhưng điều này được cho phép. x có thể được tính toán trước khi triển khai ứng dụng dựa trên bộ nhớ khả dụng và được điều chỉnh động nếu có thêm bộ nhớ.


2

Suy nghĩ thô thiển ...

Top 10 mọi thời đại

  • Sử dụng bộ sưu tập băm nơi lưu trữ số lượng cho mỗi thuật ngữ (làm sạch các điều khoản, v.v.)
  • Mảng đã sắp xếp có chứa 10 số hạng đang diễn ra, một số hạng / số được thêm vào mảng này bất cứ khi nào số hạng của một số hạng trở nên bằng hoặc lớn hơn số nhỏ nhất trong mảng

Đối với top 10 hàng tháng được cập nhật hàng giờ:

  • Sử dụng một mảng được lập chỉ mục dựa trên số giờ đã trôi qua kể từ khi bắt đầu mô-đun 744 (số giờ trong một tháng), các mục nhập của mảng bao gồm bộ sưu tập băm trong đó số lượng cho mỗi thuật ngữ gặp phải trong thời điểm giờ này được lưu trữ. Mục nhập được đặt lại bất cứ khi nào bộ đếm thời gian thay đổi
  • số liệu thống kê trong mảng được lập chỉ mục trên khung giờ cần được thu thập bất cứ khi nào bộ đếm khung giờ hiện tại thay đổi (tối đa một giờ một lần), bằng cách sao chép và làm phẳng nội dung của mảng này được lập chỉ mục trên khung giờ

Ơ ... có lý? Tôi đã không nghĩ điều này thông suốt như tôi sẽ làm trong cuộc sống thực

À vâng, quên đề cập, "sao chép / làm phẳng" hàng giờ cần thiết cho các số liệu thống kê hàng tháng thực sự có thể sử dụng lại cùng một mã được sử dụng cho top 10 mọi thời đại, một tác dụng phụ rất hay.


2

Giải pháp chính xác

Đầu tiên, một giải pháp đảm bảo kết quả chính xác, nhưng cần nhiều bộ nhớ (bản đồ lớn).

Biến thể "mọi lúc"

Duy trì một bản đồ băm với các truy vấn dưới dạng khóa và số lượng của chúng dưới dạng giá trị. Ngoài ra, giữ danh sách 10 truy vấn thường xuyên nhất cho đến nay và số lượng truy vấn thường xuyên thứ 10 (một ngưỡng).

Liên tục cập nhật bản đồ khi luồng truy vấn được đọc. Mỗi khi số lượng vượt quá ngưỡng hiện tại, hãy làm như sau: xóa truy vấn thứ 10 khỏi danh sách "Top 10", thay thế nó bằng truy vấn bạn vừa cập nhật và cập nhật cả ngưỡng.

Biến thể "tháng trước"

Giữ nguyên danh sách "Top 10" và cập nhật nó theo cách tương tự như trên. Ngoài ra, hãy giữ một bản đồ tương tự, nhưng lần này lưu trữ các vectơ có số lượng 30 * 24 = 720 (một cho mỗi giờ) dưới dạng giá trị. Mỗi giờ hãy làm như sau cho mọi khóa: xóa bộ đếm cũ nhất khỏi vectơ, thêm một bộ đếm mới (khởi tạo bằng 0) vào cuối. Xóa khóa khỏi bản đồ nếu vectơ bằng không. Ngoài ra, mỗi giờ bạn phải tính danh sách "Top 10" từ đầu.

Lưu ý: Vâng, lần này chúng tôi đang lưu trữ 720 số nguyên thay vì một, nhưng có những phím ít hơn nhiều (các biến mọi thời đại có thực sự đuôi dài).

Ước tính

Những phép tính gần đúng này không đảm bảo lời giải chính xác, nhưng ít tốn bộ nhớ hơn.

  1. Xử lý mọi truy vấn thứ N, bỏ qua phần còn lại.
  2. (Chỉ dành cho biến thể mọi thời đại) Giữ tối đa M cặp khóa-giá trị trong bản đồ (M phải lớn nhất có thể của bạn). Đó là một loại bộ nhớ cache LRU: mỗi khi bạn đọc một truy vấn không có trong bản đồ, hãy xóa truy vấn ít được sử dụng gần đây nhất bằng số 1 và thay thế bằng truy vấn hiện đang được xử lý.

Tôi thích cách tiếp cận theo xác suất trong xấp xỉ 1. Nhưng sử dụng xấp xỉ 2 (LRU cache), điều gì sẽ xảy ra nếu các thuật ngữ không phổ biến ban đầu trở nên phổ biến sau này? Chúng sẽ không bị loại bỏ mỗi khi chúng được thêm vào, vì số lượng của chúng sẽ rất thấp?
del

@del Bạn nói đúng, ước tính thứ hai sẽ chỉ hoạt động đối với một số luồng truy vấn nhất định. Nó kém tin cậy hơn, nhưng đồng thời yêu cầu ít tài nguyên hơn. Lưu ý: bạn cũng có thể kết hợp cả hai giá trị gần đúng.
Bolo

2

10 cụm từ tìm kiếm hàng đầu trong tháng qua

Sử dụng cấu trúc dữ liệu / lập chỉ mục bộ nhớ hiệu quả, chẳng hạn như các lần thử được đóng gói chặt chẽ (từ các mục nhập wikipedia về lần thử ) xác định gần đúng một số mối quan hệ giữa yêu cầu bộ nhớ và n - số thuật ngữ.

Trong trường hợp bộ nhớ cần thiết đó có sẵn ( giả định 1 ), bạn có thể giữ số liệu thống kê hàng tháng chính xác và tổng hợp hàng tháng thành thống kê mọi thời gian.

Ngoài ra, có một giả định ở đây diễn giải 'tháng trước' là thời hạn cố định. Nhưng ngay cả khi cửa sổ hàng tháng đang trượt, quy trình trên cho thấy nguyên tắc (trượt có thể được tính gần đúng với các cửa sổ cố định có kích thước nhất định).

Điều này làm tôi nhớ đến cơ sở dữ liệu tổng hợp tổng hợp ngoại trừ một số thống kê được tính theo 'mọi lúc' (theo nghĩa là không phải tất cả dữ liệu đều được giữ lại; rrd tổng hợp các khoảng thời gian bỏ qua chi tiết bằng cách tính trung bình, tổng hợp hoặc chọn giá trị tối đa / tối thiểu, trong nhiệm vụ đã cho, chi tiết bị mất là thông tin về các mục tần số thấp, có thể gây ra lỗi).

Giả định 1

Nếu chúng ta không thể nắm giữ số liệu thống kê hoàn hảo trong cả tháng, thì chúng ta sẽ có thể tìm thấy một khoảng thời gian P nhất định mà chúng ta có thể có số liệu thống kê hoàn hảo. Ví dụ, giả sử chúng ta có số liệu thống kê hoàn hảo về một khoảng thời gian P nào đó, diễn ra trong tháng n lần.
Chức năng xác định số liệu thống kê hoàn hảo f(search_term) -> search_term_occurance.

Nếu chúng ta có thể giữ tất cả ncác bảng thống kê hoàn hảo trong bộ nhớ thì số liệu thống kê hàng tháng trượt có thể được tính như sau:

  • thêm số liệu thống kê cho khoảng thời gian mới nhất
  • xóa số liệu thống kê cho khoảng thời gian cũ nhất (vì vậy chúng tôi phải giữ nbảng thống kê hoàn hảo)

Tuy nhiên, nếu chúng tôi chỉ giữ top 10 ở cấp độ tổng hợp (hàng tháng) thì chúng tôi sẽ có thể loại bỏ rất nhiều dữ liệu từ thống kê đầy đủ của khoảng thời gian cố định. Điều này cung cấp một quy trình làm việc đã cố định (giả sử giới hạn trên trên bảng thống kê hoàn hảo cho chu kỳ P) yêu cầu bộ nhớ.

Vấn đề với quy trình trên là nếu chúng tôi chỉ giữ thông tin về 10 cụm từ hàng đầu cho cửa sổ trượt (tương tự cho mọi thời điểm), thì số liệu thống kê sẽ chính xác cho các cụm từ tìm kiếm đạt đỉnh trong một khoảng thời gian, nhưng có thể không thấy thống kê cho các cụm từ tìm kiếm liên tục theo thời gian.

Điều này có thể được bù đắp bằng cách giữ thông tin về hơn 10 cụm từ hàng đầu, ví dụ như 100 cụm từ hàng đầu, hy vọng rằng 10 cụm từ hàng đầu sẽ đúng.

Tôi nghĩ rằng phân tích sâu hơn có thể liên hệ số lần xuất hiện tối thiểu cần thiết để một mục nhập trở thành một phần của thống kê (liên quan đến lỗi tối đa).

(Khi quyết định mục nhập nào sẽ trở thành một phần của thống kê, người ta cũng có thể theo dõi và theo dõi các xu hướng; ví dụ: nếu phép ngoại suy tuyến tính của các lần xuất hiện trong mỗi khoảng thời gian P cho mỗi thuật ngữ cho bạn biết rằng thuật ngữ đó sẽ trở nên quan trọng trong một hoặc hai tháng bạn có thể đã bắt đầu theo dõi nó. Nguyên tắc tương tự áp dụng cho việc xóa cụm từ tìm kiếm khỏi nhóm được theo dõi.)

Trường hợp tồi tệ nhất cho trường hợp trên là khi bạn có nhiều cụm từ thường xuyên gần như bằng nhau và chúng thay đổi liên tục (ví dụ: nếu chỉ theo dõi 100 cụm từ, thì nếu 150 cụm từ hàng đầu xảy ra thường xuyên như nhau, nhưng 50 cụm từ hàng đầu thường xuyên hơn trong tháng đầu tiên và e rằng một thời gian sau, số liệu thống kê sẽ không được lưu giữ chính xác).

Ngoài ra, có thể có một cách tiếp cận khác không cố định về kích thước bộ nhớ (nói đúng ra là không phải ở trên), sẽ xác định ý nghĩa tối thiểu về số lần xuất hiện / khoảng thời gian (ngày, tháng, năm, mọi lúc) để giữ số liệu thống kê. Điều này có thể đảm bảo sai số tối đa đối với từng số liệu thống kê trong quá trình tổng hợp (xem lại bảng tổng hợp).


2

Điều gì về sự điều chỉnh của "thuật toán thay thế trang đồng hồ" (còn được gọi là "cơ hội thứ hai")? Tôi có thể tưởng tượng nó hoạt động rất tốt nếu các yêu cầu tìm kiếm được phân phối đồng đều (điều đó có nghĩa là hầu hết các cụm từ được tìm kiếm xuất hiện thường xuyên thay vì 5 triệu lần liên tiếp và sau đó không bao giờ lặp lại).

Đây là phần trình bày trực quan của thuật toán: thuật toán thay thế trang đồng hồ


0

Lưu trữ số lượng các cụm từ tìm kiếm trong một bảng băm khổng lồ, trong đó mỗi tìm kiếm mới khiến một phần tử cụ thể được tăng thêm một. Theo dõi 20 cụm từ tìm kiếm hàng đầu; khi phần tử ở vị trí thứ 11 được tăng dần, hãy kiểm tra xem nó có cần hoán đổi vị trí với # 10 * không (không cần thiết phải sắp xếp 10 phần tử hàng đầu; tất cả những gì bạn quan tâm là vẽ ra sự khác biệt giữa thứ 10 và thứ 11).

* Cần phải thực hiện các kiểm tra tương tự để xem liệu một cụm từ tìm kiếm mới có ở vị trí thứ 11 hay không, vì vậy thuật toán này cũng giảm dần các cụm từ tìm kiếm khác - vì vậy tôi đang đơn giản hóa một chút.


Bạn sẽ muốn giới hạn kích thước bảng băm của mình. Điều gì sẽ xảy ra nếu bạn nhận được một luồng tìm kiếm duy nhất? Bạn cần chắc chắn rằng bạn không ngăn mình nhận ra một cụm từ được tìm kiếm thường xuyên nhưng không thường xuyên. Theo thời gian, cụm từ đó có thể là cụm từ tìm kiếm hàng đầu, đặc biệt nếu tất cả các cụm từ tìm kiếm khác là "sự kiện hiện tại", tức là được tìm kiếm nhiều ngay bây giờ, nhưng không nhiều vào tuần tới. Trên thực tế, những cân nhắc như thế này có thể là những ước tính mà bạn muốn thực hiện. Hãy biện minh cho chúng bằng cách nói rằng, chúng tôi sẽ không bắt những thứ này bởi vì làm như vậy khiến thuật toán tốn nhiều thời gian / không gian hơn.
cape1232

Tôi khá chắc rằng Google có số lượng tất cả mọi thứ - mặc dù vậy, một số số lượng không được duy trì tĩnh mà được tính toán khi cần thiết.
Ether,

0

đôi khi câu trả lời tốt nhất là "Tôi không biết".

Tôi sẽ có một cú đâm sâu hơn. Bản năng đầu tiên của tôi là đưa các kết quả vào Q. Một quy trình sẽ liên tục xử lý các mục đến Q. Quá trình sẽ duy trì một bản đồ

hạn -> đếm

mỗi khi một mục Q được xử lý, bạn chỉ cần tra cứu cụm từ tìm kiếm và tăng số lượng.

Đồng thời, tôi sẽ duy trì một danh sách các tham chiếu đến 10 mục hàng đầu trong bản đồ.

Đối với mục nhập hiện đã được triển khai, hãy xem số lượng của mục đó có lớn hơn tổng số mục nhập nhỏ nhất trong top 10 hay không (nếu chưa có trong danh sách). Nếu có, hãy thay thế nhỏ nhất bằng mục nhập.

Tôi nghĩ rằng sẽ làm việc. Không có hoạt động nào tốn nhiều thời gian. Bạn sẽ phải tìm cách quản lý kích thước của bản đồ đếm. nhưng điều đó sẽ đủ tốt cho một câu trả lời phỏng vấn.

Họ không mong đợi một giải pháp, mà muốn xem liệu bạn có thể suy nghĩ hay không. Bạn không cần phải viết giải pháp sau đó và ở đó ...


12
Cấu trúc dữ liệu được gọi là a queue, Qlà một chữ cái :).
IVlad

3
Nếu tôi đang thực hiện cuộc phỏng vấn, "Tôi không biết <stop>" chắc chắn sẽ không phải là câu trả lời tốt nhất. Suy nghĩ trên đôi chân của bạn. Nếu bạn không biết, hãy tìm hiểu nó - hoặc ít nhất hãy thử.
Stephen

trong các cuộc phỏng vấn, khi tôi nhìn thấy ai đó đang ngủ đông trên 7 trang tiếp tục 5 lần và họ không thể cho tôi biết ORM là gì, tôi kết thúc cuộc phỏng vấn ngay lập tức. Đúng hơn là họ không đưa nó vào sơ yếu lý lịch của họ và chỉ nói: "Tôi không biết". Không ai biết mọi thứ. @IVIad, tôi đã giả vờ tôi đã một nhà phát triển C và cố gắng tiết kiệm bit ...;)
hvgotcodes

0

Một cách là đối với mọi tìm kiếm, bạn lưu trữ cụm từ tìm kiếm đó và dấu thời gian của nó. Theo cách đó, việc tìm kiếm top 10 trong bất kỳ khoảng thời gian nào chỉ đơn giản là so sánh tất cả các cụm từ tìm kiếm trong khoảng thời gian nhất định.

Thuật toán đơn giản, nhưng hạn chế sẽ là tiêu thụ bộ nhớ và thời gian lớn hơn.


0

Điều gì về việc sử dụng Splay Tree với 10 nút? Mỗi lần bạn cố gắng truy cập một giá trị (cụm từ tìm kiếm) không có trong cây, hãy loại bỏ bất kỳ lá nào, chèn giá trị đó và truy cập nó.

Ý tưởng đằng sau điều này cũng giống như trong câu trả lời khác của tôi . Theo giả định rằng các cụm từ tìm kiếm được truy cập đồng đều / thường xuyên, giải pháp này sẽ hoạt động rất tốt.

biên tập

Người ta cũng có thể lưu trữ thêm một số cụm từ tìm kiếm trong cây (tương tự đối với giải pháp tôi đề xuất trong câu trả lời khác của tôi) để không xóa một nút có thể được truy cập lại rất sớm. Càng lưu trữ nhiều giá trị trong đó, kết quả càng tốt.


0

Dunno nếu tôi hiểu nó đúng hay không. Giải pháp của tôi là sử dụng heap. Vì có 10 mục tìm kiếm hàng đầu, tôi tạo một đống với kích thước 10. Sau đó, cập nhật đống này với tìm kiếm mới. Nếu tần suất của một tìm kiếm mới lớn hơn hàng đống (Max Heap) trên cùng, hãy cập nhật nó. Bỏ cái có tần số nhỏ nhất.

Nhưng, cách tính tần suất tìm kiếm cụ thể sẽ được tính vào một thứ khác. Có thể như mọi người đã nêu, thuật toán luồng dữ liệu….


0

Sử dụng cm-sketch để lưu trữ số lượng tất cả các tìm kiếm kể từ khi bắt đầu, giữ một đống nhỏ nhất có kích thước 10 với nó cho top 10. Để có kết quả hàng tháng, hãy giữ 30 cm-sketch / hash-table và min-heap với nó, mỗi cái bắt đầu đếm và cập nhật từ 30, 29 .., 1 ngày qua. Khi một ngày trôi qua, hãy xóa cuối cùng và sử dụng nó như ngày 1. Tương tự cho kết quả hàng giờ, giữ 60 bảng băm và min-heap và bắt đầu đếm cho 60, 59, ... 1 phút cuối cùng. Khi một phút trôi qua, hãy xóa cái cuối cùng và sử dụng nó như phút 1.

Kết quả Montly chính xác trong khoảng 1 ngày, kết quả hàng giờ chính xác trong khoảng 1 phút


0

Vấn đề không thể giải quyết một cách phổ biến khi bạn có một lượng bộ nhớ cố định và một dòng mã thông báo 'vô hạn' (nghĩ là rất lớn).

Một lời giải thích sơ lược ...

Để biết lý do tại sao, hãy xem xét một dòng mã thông báo có một mã thông báo cụ thể (tức là từ) T mỗi N mã thông báo trong luồng đầu vào.

Ngoài ra, giả sử rằng bộ nhớ có thể chứa các tham chiếu (id từ và số lượng) cho tối đa M mã thông báo.

Với các điều kiện này, có thể xây dựng một luồng đầu vào trong đó mã thông báo T sẽ không bao giờ được phát hiện nếu N đủ lớn để luồng chứa các mã thông báo M khác nhau giữa các T.

Điều này không phụ thuộc vào chi tiết thuật toán top-N. Nó chỉ phụ thuộc vào giới hạn M.

Để biết tại sao điều này đúng, hãy xem xét luồng đến được tạo thành từ các nhóm gồm hai mã thông báo giống nhau:

T a1 a2 a3 ... a-M T b1 b2 b3 ... b-M ...

trong đó a và b là tất cả các mã thông báo hợp lệ không bằng T.

Lưu ý rằng trong luồng này, chữ T xuất hiện hai lần cho mỗi ai và bi. Tuy nhiên, nó hiếm khi đủ để được xả khỏi hệ thống.

Bắt đầu với một bộ nhớ trống, mã thông báo đầu tiên (T) sẽ chiếm một vị trí trong bộ nhớ (giới hạn bởi M). Sau đó, a1 sẽ tiêu thụ một khe, tất cả các cách đến a- (M-1) khi M hết.

Khi aM đến, thuật toán phải bỏ một ký hiệu để nó là T. Biểu tượng tiếp theo sẽ là b-1, điều này sẽ khiến a-1 bị xóa, v.v.

Vì vậy, chữ T sẽ không cư trú trong bộ nhớ đủ lâu để tạo ra một số lượng thực. Nói tóm lại, bất kỳ thuật toán nào cũng sẽ bỏ sót mã thông báo có tần số cục bộ đủ thấp nhưng tần số toàn cầu cao (trên độ dài của luồng).

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.