Ефективно знаходити двійкові рядки з низькою відстанню Хеммінга у великому наборі


80

Проблема:

Враховуючи великий (~ 100 мільйонів) список непідписаних 32-розрядних цілих чисел, непідписане 32-розрядне ціле числове вхідне значення та максимальну відстань Хеммінга , поверніть усіх членів списку, що знаходяться в межах вказаної відстані Хеммінга від вхідного значення.

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

Приклад:

For a maximum Hamming Distance of 1 (values typically will be quite small)

And input: 
00001000100000000000000001111101

The values:
01001000100000000000000001111101 
00001000100000000010000001111101 

should match because there is only 1 position in which the bits are different.

11001000100000000010000001111101

should not match because 3 bit positions are different.

Мої думки поки що:

Для виродженого випадку відстані Хеммінга 0, просто скористайтеся відсортованим списком та виконайте двійковий пошук для конкретного вхідного значення.

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

Як я можу ефективно (без сканування всього списку) виявити членів списку з відстанню Хеммінга> 1.


Як щодо мутації критеріїв на очікувану відстань удару, це може зробити рекурентна функція. Наступним кроком буде отримання об'єднання двох списків ?.
XecP277,

Ось нещодавня стаття з цієї проблеми: Велика обробка запитів відстані Хеммінга .
hammar

@Eric Ви сказали: "Максимальна відстань Хеммінга 1 (значення, як правило, будуть досить малими)" . Чи можете ви сказати, що означало "зовсім маленьке" ?
Stefan Pochmann

@Eric Крім того, чи було ~ 100 мільйонів номерів унікальними, чи були дублікати?
Stefan Pochmann

@StefanPochmann: Дублікатів немає. Найбільша відстань, яка цікавить, буде 4-5.
Eric J.

Відповіді:


111

Питання: Що ми знаємо про відстань Геммінга d (x, y)?

Відповідь:

  1. Він невід’ємний: d (x, y) ≥ 0
  2. Для ідентичних входів це лише нуль: d (x, y) = 0 ⇔ x = y
  3. Це симетрично: d (x, y) = d (y, x)
  4. Він виконує нерівність трикутника , d (x, z) ≤ d (x, y) + d (y, z)

Питання: Чому нам все одно?

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

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

Примітка: Якщо ви порівнюєте відстань Хеммінга рядків фіксованої ширини, ви можете отримати значне покращення продуктивності, використовуючи вбудовані компоненти або процесор. Наприклад, за допомогою GCC ( вручну ) ви робите це:

static inline int distance(unsigned x, unsigned y)
{
    return __builtin_popcount(x^y);
}

Якщо ви потім повідомили GCC, що компілюєте для комп’ютера з SSE4a, то, я вважаю, це повинно зменшитися лише до декількох кодів операційних кодів.

Редагувати: згідно з низкою джерел, це іноді / часто повільніше, ніж звичайний код маски / зміни / додавання. Бенчмаркинг показує, що в моїй системі версія C перевищує GCC __builtin_popcountприблизно на 160%.

Додаток: Мені самому було цікаво про проблему, тому я сформулював три реалізації: лінійний пошук, дерево BK та дерево VP. Зверніть увагу, що дерева VP і BK дуже схожі. Діти вузла в дереві BK - це «оболонки» дерев, що містять точки, кожна з яких знаходиться на фіксованій відстані від центру дерева. Вузол у дереві VP має двох дочірніх організацій, одна з яких містить усі точки у сфері, центрованій в центрі вузла, а інша дочірня частина, що містить усі точки зовні. Отже, ви можете уявити вузол VP як вузол BK з двома дуже товстими «оболонками» замість багатьох тонших.

Результати були записані на моєму ПК з тактовою частотою 3,2 ГГц, і алгоритми не намагаються використовувати кілька ядер (що повинно бути легко). Я вибрав базу даних розміром 100 млн псевдовипадкових цілих чисел. Результати - це середнє значення 1000 запитів на відстань 1..5 та 100 запитів на 6..10 та лінійний пошук.

  • База даних: 100 млн псевдовипадкових цілих чисел
  • Кількість тестів: 1000 на відстань 1..5, 100 на відстань 6..10 та лінійні
  • Результати: Середнє число звернень за запитом (дуже приблизне)
  • Швидкість: кількість запитів в секунду
  • Покриття: Середній відсоток перевіреної бази даних за запитом
                - BK Tree - - Дерево VP - - Лінійне -
Dist Результати Швидкість Cov Швидкість Cov Швидкість Cov
1 0,90 3800 0,048% 4200 0,048%
2 11 300 0,68% 330 0,65%
3130 56 3,8% 63 3,4%
4970 18 12% 22 10%
5 5700 8,5 26% 10 22%
6 2.6e4 5.2 42% 6.0 37%
7 1,1e5 3,7 60% 4,1 54%
8 3,5e5 3,0 74% 3,2 70%
9 1,0e6 2,6 85% 2,7 82%
10 2,5e6 2,3 91% 2,4 90%
будь-які 2,2 100%

У своєму коментарі ви згадали:

Я думаю, що BK-дерева можна вдосконалити, створивши купу BK-дерев з різними кореневими вузлами та розповсюдивши їх.

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

Останній підказка: вузли листів у дереві повинні бути просто плоскими масивами цілих чисел для лінійного сканування. Для невеликих наборів (можливо, 1000 точок або менше) це буде швидше та ефективніше пам’яті.


9
Ура! Мій представник 10 тис. Тут ;-)
Дітріх Епп,

Я розглянув метричний простір, але відкинув його, коли зрозумів, наскільки все близько. Очевидно, що BK-дерево - це просто груба сила, і тому це не буде оптимізацією. M-дерево і дерево VP також не будуть оптимізацією через те, наскільки близько все разом. (Відстань до Хеммінга 4 відповідає відстані двох, тоді як відстань Хеммінга 2 відповідає відстані кореня два.)
Ніл Г

1
Відстань Хеммінга для цілих чисел фіксованого розміру ідентична нормі L1, якщо ви вважаєте цілі числа рядами бітів. В іншому випадку "стандартною" нормою L1 між двома струнами є сума позитивних відстаней між елементами.
Мокоша

2
@DietrichEpp Це одна з найдивовижніших відповідей, які я коли-небудь знаходив на SO. Я збирався запитати, скільки часу потрібно для побудови індексу, але потім я побачив, що ви опублікували код. Відповідь: на i7-3770K із тактовою частотою 3,5 ГГц дерево BK розміром 1 млн. Побудовано за 0,034 с, а дерево BK із 100 млн. Елементів - за 13 с. Дерева ВП займають приблизно в 4 рази більше часу, і мої шанувальники починають голосно крутитися.
Mark E. Haase

2
@StefanPochmann: Ви, схоже, переплутали кнопку "Додати ще одну відповідь" з кнопкою "Додати коментар". Погляньте внизу сторінки, там ви знайдете кнопку "Додати ще одну відповідь".
Дітріх Епп,

13

Я написав рішення, де представляю вхідні числа в бітсеті 2 32 біт, тому я можу перевірити в O (1), чи є певне число у вхідних даних. Потім для запитуваного числа та максимальної відстані я рекурсивно генерую всі числа, що знаходяться в межах цієї відстані, і перевіряю їх за бітовим набором.

Наприклад, для максимальної відстані 5 це 242825 чисел ( сума d = від 0 до 5 {32 вибрати d} ). Для порівняння, рішення VP-дерева Дітріха Еппа, наприклад, проходить через 22% від 100 мільйонів чисел, тобто через 22 мільйони чисел.

Я використав код / ​​рішення Дітріха як основу, щоб додати своє рішення та порівняти його з його. Ось швидкості, в запитах в секунду, для максимальних відстаней до 10:

Dist     BK Tree     VP Tree         Bitset   Linear

   1   10,133.83   15,773.69   1,905,202.76   4.73
   2      677.78    1,006.95     218,624.08   4.70
   3      113.14      173.15      27,022.32   4.76
   4       34.06       54.13       4,239.28   4.75
   5       15.21       23.81         932.18   4.79
   6        8.96       13.23         236.09   4.78
   7        6.52        8.37          69.18   4.77
   8        5.11        6.15          23.76   4.68
   9        4.39        4.83           9.01   4.47
  10        3.69        3.94           2.82   4.13

Prepare     4.1s       21.0s          1.52s  0.13s
times (for building the data structure before the queries)

На невеликих відстанях бітсет-рішення є найшвидшим із чотирьох. Автор запитання Ерік прокоментував нижче, що найбільша відстань інтересу, ймовірно, буде 4-5. Природно, що мій бітсет-рішення стає повільнішим на більші відстані, навіть повільнішим, ніж лінійний пошук (для відстані 32 він проходив би через 2 32 числа). Але на відстань 9 це все одно легко веде.

Я також змінив тестування Дітріха. Кожен із наведених вище результатів дозволяє алгоритму вирішити принаймні три запити та стільки запитів, скільки він може, приблизно за 15 секунд (я роблю обходи із запитами 1, 2, 4, 8, 16 тощо, доки не буде принаймні 10 секунд пройдено в цілому). Це досить стабільно, я навіть отримую подібні цифри лише за 1 секунду.

Мій центральний процесор - i7-6700. Мій код (на основі Дітріха) тут (ігноруйте документацію там принаймні поки що, не впевнені, що з цим робити, але tree.cмістить увесь код і моє test.batшоу, як я компілював і запускав (я використовував прапори Дітріха Makefile)) . Ярлик до мого рішення .

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


Найбільша відстань, яка цікавить, може бути 4-5, тому це рішення дуже цікаве. У реальному домені немає дублікатів, що надихнуло на запитання.
Eric J.

3

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

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

Отже, підсумовуємо: скажімо, у вас є купа 32-бітових рядків у БД або файлах, і ви хочете знайти кожен хеш, який знаходиться в межах 3-бітової відстані або менше від вашого бітового рядка "запиту":

  1. створити таблицю з чотирма стовпцями: кожна міститиме 8-бітовий (у вигляді рядка або int) фрагмент із 32-х бітових хешів, фрагмент від 1 до 4. Або якщо ви використовуєте файли, створіть чотири файли, кожен із яких є перестановкою фрагментів, що мають по одному "острівці" в передній частині кожного "ряду"

  2. наріжте рядок вашого запиту бітом таким же чином у qslice 1 до 4.

  3. запитувати цю таблицю так, щоб будь-який з qslice1=islice1 or qslice2=islice2 or qslice3=islice3 or qslice4=islice4. Це дає вам кожен рядок, що знаходиться в межах 7 бітів (8 - 1 ) від рядка запиту. Якщо ви використовуєте файл, виконайте двійковий пошук у кожному з чотирьох переставлених файлів для отримання однакових результатів.

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

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

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

Якщо ви працюєте в пам’яті замість файлів, ваш 100-бітний 32-бітний рядок даних буде в діапазоні 4 ГБ. Отже, для чотирьох переставлених списків може знадобитися близько 16 ГБ оперативної пам'яті. Хоча я отримую відмінні результати із накладеними на пам'ять файлами і маю менше оперативної пам'яті для наборів даних подібного розміру.

Доступні реалізації з відкритим кодом. Найкращим у космосі є IMHO той, що зроблений для Simhash Moz , C ++, але розроблений для 64 бітових рядків, а не 32 біт.

Цей підхід з обмеженою дистанцією був вперше описаний AFAIK Мойсеєм Чарікаром у його наглядовій роботі "simhash" та відповідному патенті Google :

  1. НАБЛИЖНИЙ БЛИЗЬКИЙ БЛИЗЬКИЙ ПОСУДОВИЙ ПОШУК У ВІДОМНОМУ ПРОСТОРІ

[...]

Враховуючи бітові вектори, що складаються з d бітів, ми вибираємо N = O (n 1 / (1+)) випадкові перестановки бітів. Для кожної випадкової перестановки σ ми підтримуємо відсортований порядок O σ бітових векторів у лексикографічному порядку бітів, переставлених на σ. Враховуючи вектор біта запиту q, ми знаходимо приблизного найближчого сусіда, роблячи наступне:

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

Моніка Хенцігер розширила це питання у своїй роботі "Пошук майже дублюючих веб-сторінок: масштабна оцінка алгоритмів" :

3.3 Результати для алгоритму С

Ми розділили бітовий рядок кожної сторінки на 12 4-байтових шматочків, що не перекриваються, створивши 20В фрагментів, і обчислили С-подібність усіх сторінок, які мали принаймні один спільний шматок. Цей підхід гарантовано дозволяє знайти всі пари сторінок із різницею до 11, тобто C-подібністю 373, але може пропустити деякі для більших відмінностей.

Це також пояснюється в роботі « Виявлення майже дублікатів для веб-сканування » Гурміт Сінгх Манку, Арвінд Джайн та Аніш Дас Сарма:

  1. ПРОБЛЕМА ВІДМІРЧЕНОЇ ДИСТАНЦІЇ

Визначення: Отримавши колекцію f-бітових відбитків пальців та запит відбитка F, визначте, чи відрізняється існуючий відбиток пальця від F щонайбільше k біт. (У пакетній версії вищезазначеної проблеми ми маємо набір відбитків пальців запиту замість одного запиту відбитків пальців)

[...]

Інтуїція: розгляньте відсортовану таблицю із 2-бітовими справді випадковими відбитками пальців. Зосередьтесь лише на найбільш значущих d бітах у таблиці. Перелік цих d-бітових чисел дорівнює "майже лічильнику" в тому сенсі, що (a) існує декілька 2-бітових комбінацій, і (b) дуже мало d-бітових комбінацій дублюється. З іншого боку, найменш значущі f - d біти є "майже випадковими".

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

Описана вище процедура допомагає нам знайти існуючий відбиток пальця, який відрізняється від F у k-бітових положеннях, і всі вони мають бути одними з найменш значущих f - d бітів F. Це забезпечує достатню кількість випадків. Для охоплення всіх випадків достатньо побудувати невелику кількість додаткових відсортованих таблиць, як це формально зазначено в наступному розділі.

Примітка: Я розмістив подібну відповідь на відповідне запитання, яке стосується лише БД


2

Ви можете заздалегідь обчислити всі можливі варіанти вашого оригінального списку в межах зазначеної відстані збиття та зберегти його у фільтрі цвітіння. Це дає вам швидке "НІ", але не обов'язково чітку відповідь про "ТАК".

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

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


Хіба це не буде дуже малоймовірним? Присутні 2 відсотки заявок.
Neil G

1

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


2
Для відстані Хеммінга 1 це розумно, оскільки існує 32 перестановки вихідного входу (переверніть кожен біт у вихідному вході один раз). Для відстані забиття 2 існує набагато більше переставлених вхідних значень, які потрібно було б шукати.
Eric J.

2
1024 + 32 + 1 пошук - це не надзвичайно велика кількість двійкових пошуків. Навіть 32 ^ 3 пошукові запити - це не так багато.
τεκ

@EricJ - Однак є 100 мільйонів даних. Це все ще розумно - з огляду на те, що плакат стверджує, що "вартість побудови структури даних є другорядною" - для розумної відстані.
сумно

Див. Біт-рядок-найближчий-сусід-пошук , який використовує різні сорти, а потім двійковий пошук.
Деніс

1

Одним із можливих підходів до вирішення цієї проблеми є використання структури даних з набором Disjoint . Ідея полягає в об’єднанні членів списку з відстанню Хеммінга <= k в тому ж наборі. Ось схема алгоритму:

  • Для кожного члена списку обчисліть усі можливі значення з відстанню Хеммінга <= k. Для k = 1 існує 32 значення (для 32-розрядних значень). Для значень k = 2, 32 + 32 * 31/2.

    • Для кожного обчисленого значення перевірте, чи воно знаходиться у вихідному введенні. Для цієї перевірки можна використовувати масив розміром 2 ^ 32 або хеш-карту.

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

    • Зберігайте кількість операцій об’єднання, що виконуються у змінній.

Ви запускаєте алгоритм з N неперервних наборів (де N - кількість елементів на вході). Кожного разу, коли ви виконуєте операцію об’єднання, ви зменшуєте на 1 кількість неперервних наборів. Коли алгоритм закінчується, структура даних з неперервною множиною матиме всі значення з відстанню Хеммінга <= k, згруповані в непересечені набори. Цю структуру даних з неперервною сукупністю можна обчислити майже за лінійний час .


Я не розумію. Якщо для вашого вхідного набору встановлено значення {11000000, 0110000, 00110000, 00011000, 00001100, 00000110, 00000011} і k = 2, я думаю, ваш алгоритм об’єднає кожен елемент із наступним сусідом (вони мають відстань Хеммінга 2), тим самим об’єднуючи їх усіх . Але 11000000 та 00000011 не мають відстані Хеммінга 2; їх відстань Хеммінга дорівнює 4. Основна проблема використання непересечених лісів (об'єднання-знахідка) полягає в тому, що близькість не є відношенням еквівалентності.
Йонас Келькер,

Влучне зауваження! Але ви повинні врахувати, що кожен елемент обробляється послідовно, і як тільки знайдено збіг, відповідний елемент буде видалено зі списку. Отже, у вашому прикладі, після операції об’єднання між 11000000 та 01100000, остання буде недоступна для об’єднання з 00110000. У результаті ви отримаєте 5 наборів і порівняєте вхідні дані лише з одним репрезентативним елементом кожного набору.
Марсіо Фонсека

Я не розумію вашої пропозиції. Може, ви могли б це закодувати (для якогось малого значення n)? Ось те, що потрібно перевірити: якщо у вас є чотири члени списку x, y, z, w, кожен із віддаленою відстані 3 до наступного, а ваша відстань запитування становить 5, чи буде x та y належати до одного класу еквівалентності (тобто дерево-об'єднання-знайти)? Чи буде y та z? Буде z і w? Як ви використовуєте класи еквівалентності, щоб вирішити, що виводити? Наскільки я можу зрозуміти, якщо ви використовуєте union-find для чого-небудь, що використовується для дедуплікації результату, що, на мою думку, хеш-набір може зробити добре. Але я не впевнений, що зрозумів?
Йонас Келькер,

1

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

Щоб здійснити запит, починайте з відстані бюджету dта введеного слова w. Для кожного сегмента на верхньому рівні зі значенням байта bобчисліть відстань Хеммінга d_0між bі старшим байтом w. Рекурсивно d - d_0виконайте пошук у цьому сегменті з бюджетом : тобто для кожного байтового значення b'нехай d_1буде відстань Хеммінга між b'і другим байтом w. Рекурсивний пошук у третьому шарі з бюджетом d - d_0 - d_1тощо.

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

Ось один із способів представити зовнішню структуру кордону сегмента: мати масив довжиною 16_777_216 ( = (2**8)**3 = 2**24), де елемент в індексіi є початковим індексом сегмента, що містить значення в діапазоні [256 * i, 256 * i + 255]. Щоб знайти індекс за кінцем цього сегмента, шукайте індекс i + 1 (або використовуйте кінець масиву для i + 1 = 2 ** 24).

Бюджет пам’яті становить 100 м * 4 байта на слово = 400 Мб для входів, і 2 ** 24 * 4 байта на адресу = 64 Мб для структури індексування, або всього лише сором’язливо половини гіга. Структура індексації складає 6,25% накладних витрат на необроблені дані. Звичайно, як тільки ви побудували структуру індексації, вам потрібно буде зберегти лише найнижчий байт кожного вхідного слова, оскільки інші три є неявними в індексі в структуру індексування, загалом ~ (64 + 50) МБ.

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

Я спробував кілька експериментів, і це виконує приблизно так само, як і лінійний пошук, іноді навіть гірше. Стільки про цю вигадливу ідею. Ну добре, принаймні це ефективно для пам'яті.


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