Алгоритм швидкого пошуку тегів


16

Проблема полягає в наступному.

  • Існує набір простих сутностей E, до кожного з яких додається набір тегів T. Кожна організація може мати довільну кількість тегів. Загальна кількість організацій становить близько 100 мільйонів, а загальна кількість тегів - близько 5000.

Тож вихідні дані є приблизно такими:

E1 - T1, T2, T3, ... Tn
E2 - T1, T5, T100, ... Tk
..
Ez - T10, T12, ... Tl

Ці вихідні дані доволі рідко оновлюються.

  • Якось мій додаток генерує логічне вираження на таких тегах:

    T1 & T2 & T3 | (T5 &! T6)

  • Що мені потрібно - це обчислити кількість сутностей, що відповідають заданому виразу (зауважте - не сутності, а просто число). Це, звичайно, може бути не зовсім точним.

Зараз у мене є простий пошук таблиці в пам'яті, який дає мені 5-10 секунд часу виконання на одному потоці.

Мені цікаво, чи є якийсь ефективний спосіб впоратись із цими речами? Який підхід ви б рекомендували? Чи існують для цього деякі загальні алгоритми чи структури даних?

Оновлення

Трохи роз’яснень за запитом.

  1. Tоб'єкти насправді відносно короткі постійні рядки. Але це насправді не має значення - ми завжди можемо призначити деякі ідентифікатори та оперувати цілими числами.
  2. Ми їх точно можемо сортувати.

1
це T1та ж посилання на об'єкт для E1, E2і т.д.?
Реакційний

як порівняти теги? Чи можна сортувати теги так, щоб T2 < T3це завжди було правдою?
Реакційний

Чи є теги бінарними? Тобто T1є trueабо falseдля даної E, і не змінної на основі введення? (тобто Model = "V5") Або T1змінний вираз, як Model = <input>?
Бобсон

Відповіді:


4

Я б зробив це в sql, маючи таблицю зв’язків EntityCategoryміж eidпосиланням на сутність та cidкатегорію посилання за допомогою самоз'єднання:

    select count(ec1.eid)
    from EntityCategory ec1 
    left join EntityCategory ec2 on ec1.eid=ec2.eid 
    left join EntityCategory ec3 on ec1.eid=ec3.eid 
    ...
    where 
      ec1.cid={categoryId1} and 
      ec2.cid={categoryId2} and
      ec3.cid={categoryId3} ...

1
+1, це класична територія бази даних. Інша відповідь може мати розумні ідеї, як це зробити в коді вручну, але це має бути в крайньому випадку.
MSalters

Я б також вибрав sql як техніку для вирішення цього питання. Більшість баз даних досить оптимізовані під ці алгоритми :)
winkbrace

3

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

Кожен тег може бути ефективно представлений цілим числом. Кожна організація має набір тегів. Вибір правильної реалізації набору важливий - можливі як B-дерева, так і відсортовані масиви. За допомогою цього набору ми будемо робити лише тести на членство. Оскільки обидві структури роблять це в O (log t)t тегами на сутність), я вважаю за краще масиви через їх більш щільне представлення.

Тепер ми можемо фільтрувати всі сутності в операції O (n · log t · p) , де p - середня довжина шляху в дереві рішення предикатів. Це дерево рішень можна замовити таким чином, щоб швидко було прийнято рішення. Без статистичних даних можна лише визначити загальну субекспресію.

Порядок пошуку суб’єктів не дуже важливий. З іншого боку, може бути вигідним сортувати його таким чином, щоб сутності, що мають індекси 0для iвсіх, мали певний тег, а решта - ні. Це зменшує n під час пошуку цього конкретного тегу (у дереві рішень це має бути перший тест). Це може бути розширено до декількох рівнів, але це ускладнює речі та займає O (2 k ) пам'ять з kрівні. При декількох рівнях слід спершу визначити теги з найбільшим коефіцієнтом посилення, де коефіцієнт посилення - це кількість об'єктів, які не повинні шукати в рази більше від ймовірності їх відкидання. Коефіцієнт посилення стає максимальним за 50:50 шансів або коли 50% суб'єктів мають цей специфічний тег. Це дозволить вам оптимізувати, навіть якщо шаблони доступу не відомі.

Ви також можете створити набори, які індексують сутності за кожним використовуваним тегом - один набір із усіма сутностями для T1, наступний для T2. Очевидна (просторова і часова) оптимізація - це зупинитись, коли набір містить більше половини всіх елементів, і зберегти ті елементи, у яких немає цього тегу, - таким чином, побудова індексів для всіх тегів займе менше ½ · n · tмісця ( t тегів усього). Зауважте, що збереження додаткових наборів може ускладнити інші оптимізації. Знову я б (сортував) масиви для наборів.

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

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

Якщо ми використовуємо відсортовані масиви для реалізації наборів індексів, то багато етапів оцінки можуть бути реалізовані як операції злиття. T1 & T2означає, що ми беремо T1і T2списки, виділяємо цільовий масив розміром більших входів і виконуємо наступний алгоритм, поки обидва входи не будуть порожніми: Якщо T1[0] < T2[0], тоді T1++(відкиньте голову). Якщо T1[0] > T2[0]тоді T2++. Якщо обидва головки рівні, то скопіюйте цей номер в цільовому масив, і збільшують всі три покажчика ( T1, T2, цільові). Якщо присудок є T1 | T2, то жодні елементи не відкидаються, а менші копіюються. Предикат форми T1 & ¬T2також може бути реалізований з використанням стратегії злиття, але ¬T1і T1 | ¬T2не може.

Це слід враховувати при впорядкуванні дерева рішень про предикати: Доповнення повинні відбуватися або на RHS &, або в кінці, коли визначається остаточне підрахунок і фактичні елементи не потрібно дивитись.

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

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

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

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