* Алгоритъм на Флажоле-Мартин за определяне на кардиналност на мултимножество
Публикувано на 29 юни 2023 в раздел Информатика.
Мултимножествата са множества, за които повторенията от елементи се отчитат и са от значение. Например мултимножеството {1, 1, 1, 2} е различно от мултимножеството {1, 1, 2}, защото имат различни елементи в него. Подредбата обаче няма значение. За такива множества често ни интересува т.нар. кардиналност (мощност): броят на различните елементи. Или казано по-просто - ако вземем едно цялото количество от всички обекти в едно мултимножество, колко от тях са различни?
Нека демонстрираме с пример. Ще създадем един масив в Java с 10_000_000 елемента, който ще запълним с произволни числа (които ще са нашите обекти). Естествено и очаквано е някои от числата, които добавим, да се повтарят.
int setSize = 10_000_000; int possibleElements = Integer.MAX_VALUE; Integer[] myMultiset = new Integer[setSize]; Random r = new Random(); for(int i=0; i<setSize; i++){ myMultiset[i] = r.nextInt(possibleElements); }
Колко са уникалните числа в масива? Eдин подход към решаването на тази задача е да се създаде паралелна структура от данни списък. След това един по един се взимат елементите от множеството и се добавят в списъка, но само и единствено ако в списъка все още няма същия повтарящ се елемент. По този начин повторенията ще бъдат премахнати и в списъка ще останат само уникалните елементи. Проблемът на това решение е, че отнема изключително много процесорно време при големи мултимножества (заради проверките на всеки елемент - ще видите, че дори този пример е прекалено бавен и вероятно няма да пожелаете да го изчакате):
List<Integer> uniquesList = new LinkedList<>(); for(Integer i: myMultiset){ if(!uniquesList.contains(i)){ uniquesList.add(i); } } System.out.println("Unique elements: "+uniquesList.size());
Алтернативен подход е паралелната структура да е хеш таблица. По този начин няма да правим проверки за всеки елемент поотделно, а директно ще го добавяме и, в случай на повторение, ще презаписваме отгоре. С това процесорното време се намалява драстично, но за сметка на това се увеличава многократно заеманата памет.
Set<Integer> uniquesSet = new HashSet<>(); for(Integer i: myMultiset){ uniquesSet.add(i); } System.out.println("Unique elements: "+uniquesSet.size());
За разлика от двата описани метода, алгоритъмът на Флажоле-Мартин е вероятностен. Той не дава точен отговор (не може да кажем със сигурност каква е точната кардиналността на мултимножеството), а дава само приблизителен. За сметка на това не отнема нито много процесорно време, нито заема допълнителна памет. Неговото действие е следното:
- Избира се хеш функция h(x), която на всеки подаден обект x ще съпоставя бинарно число с дължина L, която е с максимално добра ентропия (генерираната поредица от нули и единици изглежда максимално непредвидима);
- Дефинира се функция p(b), която връща броят на нулите в края на подадено бинарно число b;
- Създава се бинарен масив BM с L на брой елемента, който първоначално е нулиран (всички елементи са 0);
- За всеки елемент x от даденото мултимножество се изчислява i=p(h(x)), след което в масива се задава BM[i]=1;
- След като калкулацията е извършена за всички елементи се намира най-малкото число n, такова че BM[n]==0;
- Числото 2n/0.77351 се приема за броят на различните елементи в мултимножеството.
Оригиналната статия, където алгоритъмът е описан подробно, е следната:
Flajolet, P., & Martin, G. N. (1985). Probabilistic counting algorithms for data base applications. Journal of computer and system sciences, 31(2), 182-209.
Оказва се, че при много големи множества, точността на отговора на този алгоритъм е задоволителна, но все пак може да варира значително. Един исторически често използван вариант за подобрение е да се приложи през няколко различни хеш функции, след което резултатът да се усредни. Друг вариант е да се направи същото и да се вземе медианата. Използвал се е и смесен вариант между двете.
Най-популярната реализация с подобрение, която се използва в днешно време, е т.нар. HyperLogLog алгоритъм. При него мултимножеството се разделя на подмножества, изчислява се кардиналността на всяко едно по алгоритъма на Флажоле-Мартин, след което се изчислява тяхното среднохармонично число (по този начин се дава по-голяма тежест на по-малките числа). Алгоритъмът e показал, че дава много точни приближения при много големи мултимножества. Описан е подробно в следната статия:
Flajolet, P., Fusy, É., Gandouet, O., & Meunier, F. (2007, June). Hyperloglog: the analysis of a near-optimal cardinality estimation algorithm. In Discrete Mathematics and Theoretical Computer Science (pp. 137-156). Discrete Mathematics and Theoretical Computer Science.
Не само това, но забележете, че HyperLogLog много лесно се реализира като паралелен алгоритъм - просто всяко отделно изчисление се пуска в отделна нишка.
Къде може да се прилага този алгоритъм в практиката? Навсякъде, където е нужно приложение на функцията COUNT(DISTINCT *) -- по аналогия от SQL, -- но обемът на данните е изключително голям, а абсолютната точност не е от изключително значение. Най-често приложение получава при нерелационни бази от данни.
Добави коментар