Главная | Все статьи | Код

Фильтр Блума: зачем нужен и как работает

Время чтения статьи ~11 минут 15
Фильтр Блума: зачем нужен и как работает главное изображение

Проверка наличия объекта — одна из ключевых задач в программировании. Она встречается при разработке практически любого программного продукта. Чтобы ускорить поиск объекта, разработчики применяют фильтр Блума. В этой статье мы расскажем, почему появился фильтр Блума и в каких случаях его применяют. Также познакомим с принципами работы с фильтром и научим пользоваться им.

Эта тема не входит в стандартную программу подготовки программистов. Если вы изучите ее и примените на практике, то станете более востребованным специалистом.

Как ищут объект

Чтобы найти объект, применяют разные способы. Можно сделать последовательный перебор или использовать хеш-таблицы. При этом каждый из этих способов применяется в определенных случаях, и у них есть ограничения.

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

Предположим, вы играете с другом в «Города». Вы плохо знаете географию и поэтому не можете утверждать, что Норфолк — это не город.

Программист решил бы эту проблему так: разработал программу, загрузил в нее справочник всех городов планеты и выполнил поиск. Но это не понравится сопернику, так как ему придется долго ждать.

Допустим, поиск города в неотсортированном списке занимает время О(N). Всего в мире насчитывается 2 667 417 городов. Предположим, что наш компьютер может выполнять 1 000 операций в секунду. Тогда время на поиск составит 2667,5 секунды — более 44 минут.

Это слишком долго для игры в «Города», поэтому нужно ускорить процесс. Например, купить компьютер помощнее, но тогда придется потратиться. Решить вопрос можно менее затратно — улучшить алгоритм, который сократит время поиска.

Например, можно использовать хеш-таблицы, чья скорость поиска в идеальном случае считается равной О(1). Только она требует больших затрат по памяти и зависит от выбранного разработчиком метода хеширования.

Из-за того, что в программировании не было идеального способа решения такой проблемы, придумали вероятностную структуру данных — фильтр Блума. С его помощью не ищут объект, а проверяют, что его не существует. Еще он решает поставленную задачу за время О(1) и не размещает все объекты в оперативной памяти.

Что такое фильтр Блума и как он работает

Фильтр Блума — это инструмент разработчика, который ускоряет проверку наличия объекта.

Фильтр состоит из двух частей: нескольких хеш-функций, которые преобразуют элементы, добавляемые в фильтр, и массива битов. Результатом вычисления хеш-функций будут позиции в массиве, значения которых будут равны единице.

Проверка вхождения проходит аналогично: к искомому элементу применяются те же хеш-функции. При этом номера полученных адресов сравниваются с соответствующими адресами в массиве битов.

Далее события могут развиваться по двум вариантам:

1) Хотя бы в одном из адресов содержится ноль — объекта нет в исходной последовательности.

2) Во всех адресах содержатся только единицы — искомый объект, возможно, присутствует в исходной последовательности.

Например, так выглядит фильтр последовательности трех элементов — x, y и z:

Фильтр состоит из трех хеш-функций и битового массива длиной 18. Когда элемент добавляется, к нему применяется по три хеш-функции — они возвращаются в результате позиции в массиве.

В местах, где указываются функции, устанавливаем значение элемента массива равным единице. Далее проверяется наличие элемента w.

В нашем примере проверка завершается неудачно, так как хеш-функции вернули позиции массива, в одной из которых содержится ноль.

Вернемся к игре, где один из соперников назвал город Норфолк. Чтобы узнать, есть ли такой город, применим фильтр Блума.

Составим полный список всех городов мира и применим для каждого элемента хеш-функции. У нас получится следующий массив:

Проверим работу программы для нашего списка на Москве:

Во всех адресах содержатся единицы — город Москва, возможно, есть в списке.

Теперь проверим наличие Норфолка в базе:

Одна из ячеек содержит ноль — города Норфолк нет в списке. Чтобы вычислить это, нам понадобились доли секунды, а не 40 минут для перебора каждого элемента списка.

Фильтр Блума ускоряет поиск объекта, что важно для разработки. При этом у него есть побочный эффект — результат бывает неопределенным. Например, когда хеш-функции вернули все позиции массива с единицей. Это означает, что объект, возможно, есть в исходной последовательности. Такой побочный эффект — главный недостаток фильтра Блума.

Какие у фильтра Блума недостатки и ограничения

У фильтра Блума есть важное преимущество: он экономит память и увеличивает скорость проверки информации о наличии конкретного объекта. Но из-за этого проявляется существенный недостаток.

Фильтр работает с косвенными данными, которые вычисляются на основе хеш-функций. Они подвергаются коллизиям — когда при разных входных документах с некоторой частотой выдаются одинаковые ответы.

Из-за этого недостатка у фильтра Блума появляется ограничение — вероятность ошибки. Фильтр отвечает, что объект, возможно, есть, хотя его нет в изначальной последовательности.

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

Представим, что мы продолжаем играть в «Города» и решили уменьшить вероятность ложноположительного ответа.

Если мы подберем оптимальное количество хеш-функций, вероятность ошибки составит 0,001 — 0,1%. Это значение нашли через вероятность ложноположительного срабатывания.

Получается, что на каждую тысячу названных игроками городов только один будет определен некорректно — возможно существующий. Это можно признать допустимым для игры.

Фильтр Блума все равно выдаст неопределенный ответ, если уменьшить вероятность ошибки.

Если нам нужен точный ответ, то можно выполнить традиционный поиск в исходной последовательности. Поэтому классические методы выбора оптимального алгоритма поиска все равно применяются.

Фильтр Блума сэкономит время на обращение к диску и уменьшит их частоту. Но это можно применить только в качестве предварительного фильтра запросов к основной последовательности.

В таком виде структура данных используется в поисковых движках типа Google. С ее помощью можно не обращаться к ресурсам, на которых гарантированно отсутствует информация из поискового запроса.

Несмотря на то, что фильтр Блума придумали более 50 лет назад, он до сих пор активно применяется разработчиками. Рассмотрим, где с ним можно столкнуться.

Где применяется фильтр Блума

По сравнению со старыми компьютерами, вычислительная мощность современных устройств выросла. При этом увеличилась и сложность задач. Некоторые из них могут решаться несколько дней из-за больших данных. В итоге запросы долго обрабатываются, памяти не хватает, а оборудование быстро изнашивается.

В этом случае помогает фильтр Блума, который применяется в следующих случаях:

1) Поиск в интернете. Здесь фильтр позволяет поисковой машине не сканировать ресурсы, на которых точно нет информации из поискового запроса. Это экономит время пользователя.

2) Таблицы маршрутизации. Например, черные списки IP-адресов, куда нельзя перенаправлять запросы. Эту информацию нужно получать максимально быстро. Ниже на рисунке пример проверки двух IP-адресов в черном списке:

Адреса 112.64.90.12 точно нет в списке, так как одна из функций вернула позицию с нулевым значением. Для адреса 178.23.12.63 нужно уточнение, так как фильтр отвечает, что адрес, возможно, есть в черном списке. Если бы маршрутизатор проверял каждый из адресов полностью, скорость работы интернета была бы значительно ниже

3) Системы проверки орфографии. В этом случае компьютер может выделить слово, которого нет в словаре. Так не придется нагружать устройство после каждого нажатия клавиши.

4) Криптокошельки. Фильтр Блума используется, чтобы искать транзакции. В результате с некоторой вероятностью можно утверждать, что транзакция в наличии, или ее нет.

5) В биоинформатике. Фильтр используется, чтобы искать фрагменты в последовательностях ДНК. Ниже на рисунке видно, как в фильтр Блума добавили цепочку ДНК ACCTAG и искали фрагмент CGTAT:

Поиск завершается неудачей, так как различается последняя буква в искомой последовательности аминокислот и исходной последовательности ДНК

Список случаев, где применяется фильтр Блума, постоянно пополняется. В сети можно найти публикации об открытии новых мест применения этой структуры данных.

Теперь посмотрим, как фильтр Блума применяется на практике. Поработаем с JavaScript.

Как фильтр Блума реализуется на JavaScript

Работу с фильтром Блума будем рассматривать по шагам.

1) Для начала создадим новый класс, который будет представлять нашу структуру данных. Также создадим к нему конструктор, который в качестве исходных параметров будет принимать размерность массива и количество используемых хеш-функций.

В свойствах конструктора сохраним входные значения и создадим массив, который проинициализируем нулевыми значениями:

/**
 * Пример реализации «фильтра Блума»
 */
class BloomFilter{
    /**
     * @param size Размер битового массива для хранения адресов
     * @param funcCount Количество функций для хеширования входной строки
     */
    constructor(size, funcCount){
        this.size = size;
        this.funcCount = funcCount;
        this.bitArray = new Array(size);
        this.initiateArray();
    }

    /**
     * Создаем битовый массив и предзаполняем его нулями
     */
    initiateArray(){
        for(let i = 0; i < this.bitArray.length; i++){
            this.bitArray[i] = 0;
        }
    }

2) Далее реализуем операцию, которая добавляет элемент исходной последовательности в фильтр. Для этого захешируем исходный объект N раз с помощью различных хеш-функций и по каждому полученному адресу установим значение бита равным единице.

Чтобы упростить реализацию, мы используем одну хеш-функцию. Она будет незначительно изменять алгоритм хеширования в зависимости от номера функции, с которым ее вызвали.

Так как в JavaScript нет встроенного метода хеширования строки, будем использовать метод String.hashCode().

Этот алгоритм переводит строку в число, которое суммируется из кодов символов в таблице кодировки. Так как фильтру Блума нужно несколько разных хеш-функций, мы модифицируем исходную строку. Для этого добавим в качестве префикса номера используемой хеш-функции:

/**
     * Реализация универсальной функции хеширования,
     * которая отличается в зависимости от номера вызова
     * @param input Входная строка
     * @param functionNumber Номер вызова
     * @returns Результат хеширования типа Number
     */
    hashString(input, functionNumber){
        let hash = 0;
        let chr;
        if (input.length === 0) return hash;
        input = functionNumber + input; // Подставляем перед исходным текстом номер вызова
        for (let i = 0; i < input.length; i++){
            chr = input.charCodeAt(i);
            hash = ((hash << 5) - hash) + chr;
            hash |= 0;
        }
        return Math.abs(hash % this.size); // Определяем адрес в битовом массиве
    }

    /**
     * Регистрируем элемент исходной последовательности в фильтре
     * @param item Элемент исходной последовательности
     */
    addItem(item){
        for (let i = 0; i < this.funcCount; i++){
            let index = this.hashString(item, i);
            this.bitArray[index] = 1;
        }
    }

3) В конце проверим наличие элемента в последовательности. Для этого прогоним хеш-функции для искомого элемента и проверим по каждому адресу значение бита. Если хоть одно значение равно нулю, то элемента точно нет в последовательности:

/**
     * Проверяем наличие искомого элемента в последовательности
     * @param item Искомый элемент
     * @returns true — «точно отсутствует», false — «отсутствует»
     */
    checkItemNotPresented(item){
        for (let i = 0; i < this.funcCount; i++){
            let index = this.hashString(item, i);
            if (this.bitArray[index] === 0) return true;
        }
        return false;
    }
}

Если бы каждое значение равнялось единице, то элемент, возможно, был в последовательности.

Вывод

Вероятностная структура данных фильтр Блума увеличивает скорость проверки информации и показывает, есть ли объект в последовательности.

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

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

Если хотите рассмотреть полную версию программной реализации, можете найти ее в Github. Так вы научитесь пользоваться фильтром Блума в полной мере и станете более востребованным специалистом.

Дополнительные материалы

  • Пример реализации, разобранный в статье
  • Основная статья с изложением идей Бертона Блума
  • Детали определения вероятности ложноположительного срабатывания
  • Имплементация алгоритма хеширования строк для JS

Никогда не останавливайтесь: В программировании говорят, что нужно постоянно учиться даже для того, чтобы просто находиться на месте. Развивайтесь с нами — на Хекслете есть сотни курсов по разработке на разных языках и технологиях

Аватар пользователя Дмитрий Гурьянов
Дмитрий Гурьянов 06 октября 2022
15
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 28 ноября
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 28 ноября
профессия
от 14 960 ₸ в месяц
Ручное тестирование веб-приложений
4 месяца
с нуля
Старт 28 ноября
профессия
от 25 000 ₸ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 28 ноября
профессия
от 24 542 ₸ в месяц
новый
Сбор, анализ и интерпретация данных
9 месяцев
с нуля
Старт 28 ноября
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Laravel
10 месяцев
с нуля
Старт 28 ноября
профессия
от 28 908 ₸ в месяц
Создание веб-приложений со скоростью света
5 месяцев
c опытом
Старт 28 ноября
профессия
от 39 525 ₸ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 28 ноября
профессия
от 25 000 ₸ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 28 ноября
профессия
новый
Автоматизированное тестирование веб-приложений на JavaScript
8 месяцев
c опытом
Старт 28 ноября