Алгоритм пошуку топ-10 пошукових термінів


115

Зараз я готуюсь до інтерв'ю, і це нагадало мені запитання, яке мені одного разу задавали в попередньому інтерв'ю, яке було приблизно таким:

"Вас попросили розробити певне програмне забезпечення для постійного відображення 10 найпопулярніших пошукових термінів в Google. Вам надається доступ до каналу, який забезпечує нескінченний потік пошукових термінів у режимі реального часу, які зараз шукаються в Google. Опишіть, який алгоритм та структури даних Ви б використали для цього реалізацію. Ви повинні створити дві варіанти:

(i) Відображення перших 10 найпопулярніших пошукових термінів за весь час (тобто з моменту, коли ви почали читати канал).

(ii) Відображайте лише 10 найпопулярніших пошукових термінів за останній місяць, оновлюваних щогодини.

Ви можете використовувати наближення, щоб отримати список перших 10, але ви повинні обґрунтувати свій вибір. "
Я бомбардував це інтерв'ю і досі не маю уявлення, як це здійснити.

Перша частина запитує 10 найчастіших пунктів у постійно зростаючій підпорядкуванні нескінченного списку. Я вивчив алгоритми вибору, але не зміг знайти жодних онлайн-версій для вирішення цієї проблеми.

У другій частині використовується обмежений список, але через велику кількість оброблюваних даних, ви не можете дійсно зберігати цілий місяць пошукових термінів у пам'яті та обчислювати гістограму щогодини.

Проблема ускладнюється тим, що список перших 10 постійно оновлюється, тож якимось чином потрібно обчислювати свої перші 10 за розсувним вікном.

Будь-які ідеї?


11
@BlueRaja - Це не дурне питання про інтерв'ю, це погана інтерпретація з боку ОП. Це не прохання про найчастіші предмети в нескінченному списку, це прохання про найчастіші пункти кінцевої послідовності нескінченного списку. Щоб продовжити свою аналогію,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 - Це, звичайно, складне питання, але я не бачу, чому це дурно - це здається представником досить типової проблеми, з якою стикаються компанії з величезними наборами даних. @IVlad - Виправлено це відповідно до вашої пропозиції, з мого боку погана формулювання!
дель

Відповіді:


47

Що ж, схоже на надзвичайно багато даних, маючи непомірну вартість для зберігання всіх частот. Коли кількість даних настільки велика, що ми не можемо сподіватися зберегти їх усі, ми вводимо домен алгоритмів потоку даних .

Корисна книга в цій галузі: Мутукрішнан - "Потоки даних: Алгоритми та програми"

Тісно пов’язане посилання на проблему, яку я вирішив із вищезазначеного: Манку, Мотвані - "Приблизні підрахунки частоти за потоками даних" [pdf]

До речі, Мотвані зі Стенфорда (редакція) був автором дуже важливої книги "Рандомізовані алгоритми" . 11-та глава цієї книги стосується цієї проблеми . Редагувати: Вибачте, погана довідка. Ця конкретна глава стосується іншої проблеми. Після перевірки, замість цього я рекомендую розділ 5.1.2 Muthukrishnan в книзі , доступні в Інтернеті.

Хе, приємне запитання для інтерв'ю.


2
+1 Дуже цікавий матеріал, на сайтах повинен бути спосіб теги "читати". Дякую, що поділились.
Рамадхер Сінгх

@Gollum: у моїх закладках є папка для читання; ви могли просто зробити це. Я знаю, що ці посилання додаються до моїх :)
Cam

+1. Алгоритми трансляції - це саме тема, і книга Муту (єдина книга, написана поки що, AFAIK) - чудова.
ShreevatsaR

1
+1. Пов'язане: en.wikipedia.org/wiki/Online_algorithm . btw, Мотвані помер недавно, тому, можливо, був автором точнішим.

Дуже дивно. Я знав його з книги, але він, напевно, мабуть, був більш відомим завдяки цьому: "Мотвані був одним із співавторів (з Ларрі Пейджем та Сергієм Бріном, і Террі Віноградом) впливової ранньої статті про алгоритм PageRank, основа для методів пошуку Google. "( en.wikipedia.org/wiki/Rajeev_Motwani )
Димитріс Андреу

55

Огляд оцінки частоти

Існує кілька відомих алгоритмів, які можуть надати оцінку частоти для такого потоку, використовуючи фіксовану кількість пам’яті. Одна з них - Часті, - Місра і Гріс (1982). Зі списку n елементів, ви знайдете всі предмети, які трапляються більше n / k разів, використовуючи k - 1 лічильники. Це узагальнення алгоритму більшості Боєра та Мура (Фішер-Зальцберг, 1982), де k - 2. Алгоритми Манку та Мотвані LossyCounting (2002) та алгоритми Metwally SpaceSaving (2005) мають схожі вимоги до місця, але можуть надати більш точні оцінки за певними умови.

Важливо пам’ятати, що ці алгоритми можуть давати лише оцінки частоти. Зокрема, оцінка Місра-Гріса може занижувати фактичну частоту на (п / к) пунктів.

Припустимо, у вас був алгоритм, який міг би позитивно ідентифікувати предмет, лише якщо він трапляється більше 50% часу. Подайте цей алгоритм потоком N різних елементів, а потім додайте ще N - 1 копії одного елемента, x , для загальної кількості 2N - 1 предметів. Якщо алгоритм каже вам, що x перевищує 50% від загальної кількості, він повинен був бути в першому потоці; якщо цього немає, x не був у початковому потоці. Для того, щоб алгоритм здійснив це визначення, він повинен зберігати початковий потік (або деякий підсумок, пропорційний його довжині)! Отже, ми можемо довести, що простір, необхідний для такого «точного» алгоритму, був би Ω ( N ).

Натомість ці алгоритми частоти, описані тут, дають оцінку, ідентифікуючи будь-який елемент, який перевищує поріг, а також деякі елементи, які опускаються під ним на певний запас. Наприклад, алгоритм більшості , використовуючи єдиний лічильник, завжди дасть результат; якщо будь-який елемент перевищує 50% потоку, він буде знайдений. Але це також може дати вам предмет, який трапляється лише один раз. Ви б не знали, не зробивши повторний пропуск даних (використовуючи, знову ж таки, один лічильник, але шукаючи лише цей елемент).

Частий алгоритм

Ось простий опис Мішра-Гріс Частого алгоритм. Demaine (2002) та інші оптимізували алгоритм, але це дає суть.

Вкажіть порогову частку, 1 / к ; будь-який предмет, який трапляється більше n / k разів, буде знайдений. Створіть порожню карту (як червоно-чорне дерево); ключі будуть пошуковими термінами, а значення будуть лічильником цього терміна.

  1. Подивіться на кожен елемент у потоці.
  2. Якщо термін існує на карті, збільште відповідний лічильник.
  3. В іншому випадку, якщо на карті менше k - 1 запису, додайте термін до карти з лічильником одиниці.
  4. Однак якщо на карті вже є k - 1 записи, зменшіть лічильник у кожному записі. Якщо під час цього процесу будь-який лічильник досягне нуля, вийміть його з карти.

Зауважте, що ви можете обробити нескінченну кількість даних із фіксованим об'ємом пам’яті (лише карта фіксованого розміру). Кількість необхідного сховища залежить лише від порогу інтересу, а розмір потоку не має значення.

Підрахунок пошукових запитів

У цьому контексті, можливо, ви буферуєте одну годину пошуку та виконуєте цей процес на даних цієї години. Якщо ви зможете зробити другий пропуск за журнал пошуку за цю годину, ви можете отримати точний підрахунок подій головних "кандидатів", визначених у першому проході. Або, можливо, добре зробити один прохід і повідомити про всіх кандидатів, знаючи, що будь-який предмет, який там має бути, включений, а будь-які додаткові послуги - це лише шум, який зникне в найближчі години.

Будь-які кандидати, які дійсно перевищують межу інтересу, зберігаються як підсумок. Зберігайте ці місячні ці підсумки, викидаючи найстарші щогодини, і ви б добре оцінили найбільш поширені пошукові терміни.


Я вважаю, що це рішення може діяти як фільтр, зменшуючи кількість пошукових термінів, які вас цікавлять. Якщо термін внесе його до карти, почніть відстежувати фактичну статистику, навіть якщо вона випадає з карти. Потім ви можете пропустити другий прохід над даними та створити відсортовану першу десятку з обмеженої статистики, яку ви зібрали.
Дельф

Мені подобається елегантний спосіб обрізання менш шуканих термінів з дерева шляхом зменшення лічильників. Але як тільки карта буде "повною", чи не знадобиться крок зменшення для кожного нового пошукового терміна, що надходить? І як тільки це почнеться, чи не призведе це до того, що нові пошукові терміни будуть швидко видалені з карти, перш ніж у них з’явиться шанс збільшення їх лічильників?
дель

1
@del - Майте на увазі, що цей алгоритм призначений для пошуку термінів, що перевищують задану порогову частоту, не обов'язково для пошуку найбільш поширених термінів. Якщо найпоширеніші терміни опускаються нижче визначеного порогу, вони, як правило, не знайдуться. Своє занепокоєння щодо вилучення нових термінів "занадто швидко" може бути пов'язане з цією справою. Один із способів поглянути на це - чи існують реальні "сигнали" за популярністю, вони помітно виділяться від "шуму". Але іноді сигналів не знайти, просто статична пошукова статистика.
erickson

@erickson - Право - те, про що я розумію, полягає в тому, що припущення за цим алгоритмом полягає в тому, що перші 10 слів рівномірно розподілені по вікну вимірювання. Але поки ви будете тримати вікно вимірювання досить малим (наприклад, 1 годину), це, мабуть, буде правильним припущенням.
дель

1
@erickson, хоча рівномірний розподіл не є вимогою, мені цікаво, як це діятиме в більш реалістичному розподілі (влада-закон, Zipf). Давайте припустимо, що у нас є N різних слів, і збережемо червоно-чорне дерево ємністю K, сподіваючись, що воно закінчиться найбільш частими термінами K. Якщо сукупна частота термінів (N - K) слів більша за сукупну частоту K найчастіших слів, дерево зрештою гарантовано містить сміття. Ви згодні?
Димитріс Андреу

19

Це один із дослідницьких проектів, які я зараз переживаю. Вимога майже точно така, як ваша, і ми розробили приємні алгоритми для вирішення проблеми.

Вхідні дані

Вхід - це нескінченний потік англійських слів або фраз (ми їх називаємо tokens).

Вихід

  1. Вивести топ N лексем, які ми бачили досі (з усіх жетонів, які ми бачили!)
  2. Виведіть вершину N лексем у історичному вікні, скажімо, за останній день чи минулий тиждень.

Застосування цього дослідження полягає у пошуку гарячої теми чи тенденцій теми у Twitter або Facebook. У нас є сканер, який сканує на веб-сайті, який генерує потік слів, які надходять у систему. Потім система виводить слова або фрази з найвищою частотою або в цілому, або в історичному періоді. Уявіть, що за останні кілька тижнів фраза "Кубок світу" з’явиться багато разів у Twitter. Так само робить "Павло восьминога". :)

Рядок в цілі

Система має ціле ідентифікатор для кожного слова. Хоча в Інтернеті майже нескінченно можливих слів, але після накопичення великого набору слів можливість пошуку нових слів стає все нижчою і нижчою. Ми вже знайшли 4 мільйони різних слів і призначили для кожного унікальний ідентифікатор. Весь цей набір даних може бути завантажений у пам'ять як хеш-таблиця, витрачаючи приблизно 300 МБ пам'яті. (Ми реалізували власну хеш-таблицю. Реалізація Java займає величезні витрати на пам'ять)

Кожну фразу тоді можна ідентифікувати як масив цілих чисел.

Це важливо, тому що сортування та порівняння за цілими числами набагато швидше, ніж на рядках.

Архівні дані

Система зберігає архівні дані для кожного маркера. В основному це пари (Token, Frequency). Однак таблиця, в якій зберігаються дані, була б такою величезною, що нам доведеться розділити таблицю фізично. Після того, як схема розділів заснована на nграм токена. Якщо маркер - це одне слово, це 1грам. Якщо маркер - це двословна фраза, це 2грам. І це продовжується. Приблизно в 4грам ми маємо 1 мільярд записів, розмір таблиці - близько 60 ГБ.

Обробка вхідних потоків

Система поглинає вхідні пропозиції, поки пам'ять не буде повністю використана (так, нам потрібен MemoryManager). Після прийняття N пропозицій і зберігання в пам'яті система робить паузу і починає токенізувати кожне речення словами та фразами. Кожен маркер (слово чи фраза) рахується.

Для високочастотних жетонів вони завжди зберігаються в пам'яті. Для менш часто зустрічаються лексем вони сортуються на основі ідентифікаторів (пам'ятаємо, що ми переводимо String в масив цілих чисел) і серіалізуємо в файл диска.

(Однак, для вашої проблеми, оскільки ви рахуєте лише слова, ви можете помістити всю карту частот слова лише в пам'ять. Ретельно розроблена структура даних споживатиме лише 300 МБ пам'яті на 4 мільйони різних слів. Деякі підказки: використовуйте діаграму ASCII для представляють рядки), і це набагато прийнятно.

Тим часом буде ще один процес, який активується, як тільки він знайде будь-який диск-файл, згенерований системою, а потім почніть його об’єднання. Оскільки файл диска відсортовано, об'єднання зайняло б подібний процес, як сортування злиття. Деякі дизайни повинні бути обережні і тут, оскільки ми хочемо уникати занадто багато випадкових дискових пошуків. Ідея полягає у тому, щоб уникнути одночасного читання (процес злиття) / запису (вихід системи), і нехай процес злиття читає форму одного диска під час запису на інший диск. Це схоже на реалізацію блокування.

Кінець дня

Наприкінці дня система матиме багато частих жетонів з частотою, що зберігаються в пам'яті, та багато інших менш частих жетонів, що зберігаються в декількох дискових файлах (і кожен файл сортується).

Система передає карту пам'яті в файл диска (сортуйте її). Тепер проблемою стає злиття набору відсортованого файлу диска. Використовуючи подібний процес, ми отримаємо один відсортований файл диска наприкінці.

Потім, остаточне завдання - об'єднати відсортований файл диска в архівну базу даних. Залежить від розміру архівної бази даних, алгоритм працює як нижче, якщо він досить великий:

   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

Інтуїція полягає в тому, що через деякий час кількість вставок стане все меншою і меншою. Все більше операцій буде стосуватися лише оновлення. І це оновлення не покарається за індексом.

Сподіваюся, що ціле пояснення допоможе. :)


Я не розумію. Який змістовний сортування чи порівняння можна було зробити в цілих ідентифікаторах слів? Чи не цифри довільні?
Димитріс Андреу

Крім того, підрахунок частот слів є найпершим прикладом у папері Google MapReduce ( labs.google.com/papers/mapreduce.html ), розв'язуючи це масштабно в кілька рядків. Ви навіть можете перемістити свої дані до програми Google goange і зробити таке MapReduce ( code.google.com/p/appengine-mapreduce )
Димитріс Андреу

@Dimitris Andreou: Сортування за цілими числами буде швидше за рядками. Це тому, що порівняння двох цілих чисел швидше, ніж порівняння двох рядків.
SiLent SoNG

@Dimitris Andreou: Картографічне зменшення Google - це приємний розподілений підхід до вирішення цієї проблеми. Ах! Дякуємо, що надали посилання. Так, нам було б добре сортувати за допомогою декількох машин. Гарний підхід.
SiLent SoNG

@Dimitris Andreou: Поки що я розглядав лише один підхід до сортування машин. Яка приємна ідея сортувати за розподілом.
SiLent SoNG

4

Ви можете використовувати хеш-таблицю, поєднану з двійковим деревом пошуку . Створіть <search term, count>словник, який повідомляє, скільки разів шукали кожен пошуковий термін.

Очевидно, що повторювати всю таблицю хешу щогодини, щоб отримати топ-10, дуже погано. Але це про Google, про який ми говоримо, тож можна припустити, що в першу десятку потраплять, скажімо, понад 10 000 звернень (хоча це, мабуть, набагато більша кількість). Тому щоразу, коли кількість пошукових термінів перевищує 10 000, вставляйте її в BST. Потім щогодини вам потрібно отримати лише перші 10 з BST, які повинні містити порівняно мало записів.

Це вирішує проблему топ-10 усіх часів.


Справді хитра частина стосується того, що один термін займає місце в місячному звіті (наприклад, "переповнення стека" може мати 50 000 звернень за останні два місяці, але лише 10 000 за останній місяць, тоді як "амазонка" може мати 40 000 за останні два місяці, але 30 000 за останній місяць. Ви хочете, щоб "amazon" перейшов до "переповнення стека" у вашому щомісячному звіті). Для цього я б хотів зберігати для всіх основних (понад 10 000 пошукових термінів за весь час) 30-денний список, який повідомляє вам, скільки разів шукали цей термін щодня. Список буде працювати як черга FIFO: ви видаляєте перший день і вставляєте новий щодня (або щогодини, але потім вам може знадобитися зберігати більше інформації, що означає більше пам'яті / місця. Якщо пам'ять не є проблемою, зробіть це, інакше піти на це "наближення"

Це виглядає як вдалий початок. Потім ви можете турбуватися про обрізку термінів, які мають> 10 000 звернень, але їх не було багато за довгий час та подібні речі.


3

випадок i)

Ведіть хештел для всіх пошукових термінів, а також відсортований список першої десятки окремо від хештелю. Щоразу, коли відбувається пошук, збільшуйте відповідний елемент у хеш-таблиці та перевірте, чи повинен цей елемент тепер перемикатися з десятим пунктом у першому десятку списку.

O (1) пошук у списку першої десятки та введення max O (log (n)) у хешбел (якщо припускати зіткнення, керовані самоврівноважуючим бінарним деревом).

випадок ii) Замість того, щоб підтримувати величезний хешбел та невеликий список, ми підтримуємо хешбел та відсортований список усіх елементів. Щоразу, коли проводиться пошук, цей термін збільшується в хешбелі, а в відсортованому списку термін можна перевірити, щоб побачити, чи повинен він перемикатися з терміном після нього. Самобілансуюче бінарне дерево може добре працювати для цього, оскільки нам також потрібно мати можливість швидко його запитувати (докладніше про це пізніше).

Крім того, ми також ведемо список "годин" у вигляді списку FIFO (черги). Кожен елемент "години" міститиме список усіх пошукових запитів, проведених протягом цієї години. Так, наприклад, наш список годин може виглядати так:

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

Потім, щогодини. Якщо список має принаймні 720 годин (це кількість годин за 30 днів), подивіться на перший елемент у списку та на кожен пошуковий термін зменшіть цей елемент у хештелі на відповідну кількість . Потім видаліть цей елемент першої години зі списку.

Тож скажімо, ми знаходимось у годині 721, і ми готові подивитися на першу годину у нашому списку (вище). Ми б декрементували безкоштовні речі на 56 у хештейлі, забавні фотографії на 321 і т.д., а потім видалили б годину 0 зі списку повністю, оскільки нам ніколи більше не потрібно буде це переглядати.

Причина, за якою ми ведемо відсортований список усіх термінів, що дозволяє швидкі запити, полягає в тому, що кожні години після того, як ми проходимо пошукові терміни від 720 годин тому, нам потрібно забезпечити, щоб список перших десяти залишався відсортованим. Отож, наприклад, коли ми декрементуємо "безкоштовні речі" на хешблеті на 56, ми перевіримо, куди він зараз належить у списку. Оскільки це бінарне дерево, що самоврівноважує, все це можна непогано виконати за час O (log (n)).


Редагувати: Точність жертвоприношення для космосу ...

Можливо, буде корисно також застосувати великий список у першому, як і у другому. Тоді ми можемо застосувати наступну оптимізацію простору в обох випадках: Запустіть завдання cron, щоб видалити всі, окрім кращих x елементів зі списку. Це призведе до зменшення вимоги до місця (а в результаті швидше зробити запити у списку). Звичайно, це призведе до приблизного результату, але це дозволено. x можна обчислити перед розгортанням програми на основі наявної пам’яті та динамічно відрегулювати, якщо буде доступно більше пам’яті.


2

Грубе мислення ...

У топ-10 за весь час

  • Використання хеш-колекції, де зберігається підрахунок для кожного терміна (оздоровити терміни тощо)
  • Сортований масив, який містить поточний 10, термін / кількість, доданий до цього масиву кожного разу, коли кількість терміна стає рівним чи більшим, ніж найменше в масиві

Для щомісячних оновлених топ-10 щогодини:

  • Використовуючи масив, індексований на кількість годин, що пройшли з моменту запуску 744 (кількість годин протягом місяця), записи масивів складаються з колекції хешу, де зберігається підрахунок за кожен термін, що виникає під час цього часового проміжку. Запис скидається щоразу, коли лічильник лічильників годин змінюється
  • статистику в масиві, індексованому на годинний проміжок, потрібно збирати щоразу, коли поточний лічильник часових інтервалів змінюється (максимум один раз на годину), копіюючи та вирівнюючи вміст цього масиву, індексованого на часових інтервалах

Помилка ... має сенс? Я не думав про це так, як в реальному житті

Так, забув згадати, щогодинне "копіювання / вирівнювання", необхідне для щомісячної статистики, може фактично повторно використовувати той самий код, який використовується для топ-10 усіх часів, приємний побічний ефект.


2

Точне рішення

По-перше, рішення, яке гарантує правильні результати, але вимагає багато пам’яті (велика карта).

Варіант "весь час"

Підтримуйте хеш-карту із запитами як ключами та їх рахунком як значеннями. Крім того, збережіть список до 10 найчастіших запитів досі та підрахунок 10-го найчастішого підрахунку (поріг).

Постійно оновлюйте карту, коли читається потік запитів. Кожен раз, коли кількість перевищує поточний поріг, виконайте наступне: видаліть 10-й запит зі списку "Топ-10", замініть його на щойно оновлений запит, а також оновіть поріг.

Варіант "Минулий місяць"

Зберігайте той самий список "Топ-10" та оновлюйте його так само, як вище. Також зберігайте подібну карту, але цього разу зберігайте вектори 30 * 24 = 720 (по одній на кожну годину). Кожну годину виконайте наступне для кожної клавіші: видаліть найстаріший лічильник з вектора, в кінці додайте новий (ініціалізований до 0). Вийміть ключ з карти, якщо вектор повністю нульовий. Крім того, щогодини вам доведеться з нуля обчислювати список "Топ-10".

Примітка. Так, на цей раз ми зберігаємо 720 цілих чисел, а не одне, але клавіш набагато менше (у варіанта за весь час справді довгий хвіст).

Наближення

Ці наближення не гарантують правильного рішення, але є меншими затратами пам'яті.

  1. Обробляйте кожен N-й запит, пропускаючи решту.
  2. (Тільки для варіантів за весь час) Майте на карті максимум пари M-значення (M має бути таким великим, як ви можете собі дозволити). Це свого роду кеш LRU: кожного разу, коли ви читаєте запит, якого немає на карті, видаліть найменш використаний нещодавно запит з підрахунком 1 та замініть його на щойно оброблений запит.

Мені подобається ймовірнісний підхід у наближенні 1. Але, використовуючи наближення 2 (кеш LRU), що станеться, якщо терміни, які не були дуже популярними, спочатку стали популярними пізніше? Чи не будуть вони відкинуті щоразу, коли їх додають, оскільки їх кількість буде дуже низькою?
дель

@del Ви маєте рацію, друге наближення працюватиме лише для певних потоків запитів. Це менш надійно, але в той же час вимагає менше ресурсів. Примітка: ви також можете комбінувати обидва наближення.
Боло

2

10 найпопулярніших пошукових термінів за останній місяць

Використання ефективної індексації / структури даних в пам’яті, наприклад, щільно упаковані спроби (з записів у Вікіпедії на спробах ) приблизно визначає деяке співвідношення між потребами пам’яті та n - кількістю термінів.

У разі, коли потрібно пам'ять доступна ( припущення 1 ), ви можете зберегти точну щомісячну статистику і агрегувати його кожен місяць на весь час статистики.

Тут також є припущення, яке тлумачить "останній місяць" як фіксоване вікно. Але навіть якщо місячне вікно ковзає, наведена вище процедура показує принцип (ковзання можна наблизити до фіксованих вікон заданого розміру).

Це нагадує мені базу даних з круглим роботом, за винятком того, що деякі статистичні дані розраховуються на "весь час" (в сенсі, що не всі дані зберігаються; rrd консолідує періоди часу, не враховуючи деталей, усереднюючи, підсумовуючи або вибираючи значення max / min, у даному завданні деталь, яка втрачається, - це інформація про низькочастотні елементи, які можуть вводити помилки).

Припущення 1

Якщо ми не можемо провести ідеальну статистику протягом цілого місяця, то ми повинні мати можливість знайти певний період P, за який ми повинні мати можливість провести ідеальну статистику. Наприклад, якщо припустити, що ми маємо досконалу статистику за певний проміжок часу Р, який переходить в місяць n разів.
Ідеальна статистика визначає функцію f(search_term) -> search_term_occurance.

Якщо ми можемо зберегти всі nідеальні статистичні таблиці в пам'яті, то розсувні щомісячні статистичні дані можна обчислити так:

  • додати статистику за найновіший період
  • видалити статистику для найдавнішого періоду (тому ми повинні зберігати nідеальні таблиці статистики)

Однак якщо ми будемо тримати лише 10 перших на агрегованому рівні (щомісяця), ми зможемо викинути багато даних з повної статистики за фіксований період. Це дає вже працюючу процедуру, яка встановила (припускаючи верхню межу ідеальної таблиці статистики для періоду P) пам'яті.

Проблема з вищевказаною процедурою полягає в тому, що якщо ми зберігаємо інформацію лише про 10 найпопулярніших термінах для розсувного вікна (аналогічно за весь час), то статистика буде правильною для пошукових термінів, які досягають піку за певний період, але вони можуть не бачити статистика пошукових термінів, які постійно обробляються.

Це можна компенсувати, зберігаючи інформацію про більш ніж 10 найпопулярніших термінів, наприклад, найкращих 100 термінів, сподіваючись, що топ-10 буде правильним.

Я думаю, що подальший аналіз міг би пов’язати мінімальну кількість подій, необхідних для того, щоб запис став частиною статистики (що пов'язано з максимальною помилкою).

(Вирішуючи, які записи мають стати частиною статистики, можна також відстежувати та відстежувати тенденції; наприклад, якщо лінійна екстраполяція подій у кожному періоді P для кожного терміну говорить про те, що термін стане значущим через місяць або два може вже почати відстежувати його. Аналогічний принцип застосовується для видалення пошукового терміна з відстеженого пулу.)

Найгіршим випадком є ​​вище, коли у вас багато майже однаково частих термінів, і вони змінюються весь час (наприклад, якщо відстежувати лише 100 термінів, то якщо найпопулярніші терміни 150 трапляються однаково часто, але топ-50 частіше зустрічаються в перший місяць і щоб часто через деякий час статистику не вести правильно).

Також може бути інший підхід, не закріплений за розміром пам’яті (добре строго кажучи, не зазначено і вище), який би визначав мінімальну значимість щодо подій / періоду (день, місяць, рік, весь час), для якого слід зберігати статистика. Це може гарантувати максимум помилок у кожній статистиці під час агрегації (див. Ще раз круглень).


2

А як щодо адаптації "алгоритму заміни тактової сторінки" (також відомого як "другий шанс")? Я можу уявити, що це спрацює дуже добре, якщо пошукові запити розподіляються рівномірно (це означає, що більшість пошукових термінів з’являються регулярно, а не 5 мільйонів разів поспіль, а потім ніколи більше).

Ось наочне зображення алгоритму: алгоритм заміни годинника


0

Зберігайте кількість пошукових термінів у гігантській хеш-таблиці, де кожен новий пошук призводить до збільшення певного елемента на одиницю. Слідкуйте за найпопулярнішими пошуковими термінами 20 або більше коли елемент на 11-му місці збільшується, перевірте, чи потрібно йому поміняти місцями позиціями №10 * (не потрібно тримати впорядковані топ-10; все, що вам цікаво, це провести відмінність між 10-м та 11-м).

* Аналогічні перевірки потрібно зробити, щоб дізнатись, чи є новий пошуковий термін на 11-му місці, тому цей алгоритм також переходить на інші пошукові терміни, тому я трохи спрощую.


Ви хочете обмежити розмір вашої хеш-таблиці. Що робити, якщо ви отримуєте потік унікальних пошуків? Ви повинні бути впевнені, що не заважаєте собі помічати термін, який шукають регулярно, але нечасто. З часом це може стати першою пошуковою фразою, особливо якщо всі інші пошукові терміни є "поточними подіями", тобто шукають багато зараз, але не так вже й на наступному тижні. Насправді такі міркування можуть бути приблизними, які ви хочете зробити. Обґрунтуйте їх, сказавши, що ми не будемо вловлювати подібні речі, оскільки це робить дорогоцінність алгоритму дорогою / простором.
cape1232

Я впевнений , що Google має лічильник все - деякі лічильники не підтримуються статичний , хоча, а обчислюються по мірі необхідності.
Ефір

0

іноді найкраща відповідь - «я не знаю».

Я забираю глибший удар. Першим моїм інстинктом було б введення результатів у Q. Процес буде постійно обробляти елементи, що надходять у Q. Процес підтримував би карту

термін -> рахувати

щоразу, коли елемент Q обробляється, ви просто шукаєте пошуковий термін і збільшуєте кількість.

У той же час я б підтримував список посилань на 10 кращих записів на карті.

Для запису, який був реалізований на даний момент, подивіться, чи є його кількість більшим, ніж кількість підрахунків найменшого запису у верхній 10. (якщо вже не в списку). Якщо він є, замініть найменший на запис.

Я думаю, що це спрацювало б. Жодна операція не займає багато часу. Вам доведеться знайти спосіб керувати розміром карти підрахунку. але це повинно бути достатньо добре для відповіді на інтерв'ю.

Вони не очікують рішення, хочуть побачити, чи можете ви подумати. Ви не повинні писати рішення тоді і там ....


12
Структура даних називається а queue, Qє буквою :).
IVlad

3
Якби я проводив інтерв'ю, "я не знаю <стоп", це точно не було б найкращою відповіддю. Подумайте на ногах. Якщо ви не знаєте, розібрайтеся - або принаймні спробуйте.
Стівен

під час інтерв'ю, коли я бачу, що хтось із сплячим на 7 сторінках резюме 5 разів, і вони не можуть сказати мені, що таке ORM, я закінчую інтерв'ю негайно. Скоріше, вони не додають це до свого резюме, а просто кажуть: "Я не знаю". Ніхто не знає всього. @IVIad, я робив вигляд, що я розробник C і намагаюся зберегти біти ...;)
hvgotcodes

0

Один із способів полягає в тому, що для кожного пошуку ви зберігаєте цей пошуковий термін та його часову позначку. Таким чином, пошук першої десятки за будь-який проміжок часу є просто питанням порівняння всіх пошукових термінів за даний період часу.

Алгоритм простий, але недоліком було б більша пам’ять і витрата часу.


0

Що з використанням дерева Splay з 10 вузлами? Кожен раз, коли ви намагаєтеся отримати доступ до значення (пошукового терміну), яке не міститься у дереві, викидайте будь-який листок, замість цього вставляйте значення та отримуйте доступ до нього.

Ідея, що стоїть за цим, така ж, як і в іншій моїй відповіді . За припущенням, що до пошукових термінів можна отримати доступ рівномірно / регулярно, це рішення має працювати дуже добре.

редагувати

Можна також зберегти ще кілька пошукових термінів у дереві (те саме стосується рішення, яке я пропоную у своїй іншій відповіді), щоб не видалити вузол, до якого можна отримати доступ дуже скоро. Чим більше цінностей зберігає в ньому, тим кращі результати.


0

Не знаю, я правильно це розумію чи ні. Моє рішення - це купа. Через 10 найпопулярніших елементів пошуку я створюю купу розміром 10. Потім оновіть цю групу новим пошуком. Якщо частота нового пошуку перевищує верхню частину (Max Heap), оновіть її. Відмовитися від тієї, що має найменшу частоту.

Але, як обчислити частоту конкретного пошуку, буде розраховано щось інше. Можливо, як усі заявили, алгоритм потоку даних….


0

Використовуйте ескіз см для зберігання підрахунку всіх пошуків з початку, збережіть міні-купу розміром 10 з ним у верхній частині 10. Для щомісячного результату зберігайте 30 см-ескіз / хеш-таблицю та міні-купу з нею, кожен запускаючи підрахунок та оновлення від останніх 30, 29 .., 1 день. Як пройде день, очистіть останній і використовуйте його як день 1. Те ​​саме для годинного результату, тримайте 60 хеш-таблиць і min-heap і починайте рахувати за останні 60, 59, ... 1 хвилину. Пройдіть хвилину, очистіть останню і використовуйте її як 1 хвилину.

Монільний результат точний в межах 1 дня, почасовий результат точний в межах 1 хв


0

Проблема не є загальновирішуваною, коли у вас фіксований об'єм пам'яті та "нескінченний" (думаю, дуже великий) потік жетонів.

Грубе пояснення ...

Щоб зрозуміти, чому, розглянемо потік токена, який має певний маркер (тобто слово) T кожні N лексем у вхідному потоці.

Крім того, припустимо, що пам'ять може містити посилання (ідентифікатор слова і лічильники) щонайбільше M лексем.

За цих умов можна побудувати вхідний потік, де маркер T ніколи не буде виявлений, якщо N досить великий, щоб потік містив різні M лексеми між T.

Це не залежить від деталей алгоритму верхнього N. Це залежить лише від межі М.

Щоб зрозуміти, чому це правда, розглянемо вхідний потік, що складається з груп з двох однакових лексем:

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

де знаки "a" та "b" - усі дійсні лексеми, не рівні T.

Зауважте, що в цьому потоці T відображається двічі для кожного ai та bi. Тим не менш, це здається досить рідко, щоб бути спущеним з системи.

Починаючи з порожньої пам'яті, перший маркер (T) займе проріз в пам'яті (обмежений М). Тоді a1 буде споживати проріз, аж до a- (M-1), коли M вичерпано.

Коли aM надійде, алгоритм повинен скинути один символ, нехай це буде T. Наступним символом буде b-1, що призведе до змивання a-1 і т.д.

Отже, T не залишиться жителем пам'яті достатньо довго, щоб створити реальну кількість. Коротше кажучи, будь-який алгоритм пропустить маркер достатньо низької локальної частоти, але високої глобальної частоти (по довжині потоку).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.