bloom-filters

repository·master·Indexed 19 days ago

https://github.com/callidon/bloom-filters

A JavaScript/TypeScript implementation of probabilistic data structures for high-performance membership testing and cardinality estimation. Version 4.0.0 includes Bloom Filters (Classic, Partitioned, Scalable, Counting), Cuckoo Filters, XOR Filters, HyperLogLog, Count-Min Sketch, Top-K, and MinHash. Supports Node.js v4.0.0+, Chrome v41+, Firefox v34+, and Edge v12+.

Tokens
16.3K
Snippets
51
Records
59
Agent score
64%

What's inside bloom-filters

  1. Use custom hashing functions

    master

    The package uses xxh64 from @node-rs/xxhash by default. To use a custom hashing implementation, extend the Hashing class and override the serialize method. You can then apply this custom hashing logic to a data structure by assigning a new instance to the internal _hashing property.

    const {BloomFilter, Hashing} = require('bloom-filters')
    
    class CustomHashing extends Hashing {
      serialize(_element, _seed) {
        return BigInt(1)
      }
    }
    
    const bl = BloomFilter.create(2, 0.01)
    // override the hashing implementation locally
    bl._hashing = new CustomHashing()
    bl.add('a')
  2. Export and import data structures via JSON

    master

    All data structures in bloom-filters can be serialized to and from JSON. This allows you to save the state of a filter to a file, database, or transmit it over a network.

    • Use the instance method saveAsJSON() to export the current state into a JSON object.
    • Use the static method fromJSON(json) on the class to reconstruct the data structure from a JSON object.
    const {BloomFilter} = require('bloom-filters')
    
    const filter = new BloomFilter(15, 0.01)
    filter.add('alice')
    
    // export a bloom filter to JSON
    const exported = filter.saveAsJSON()
    
    // import the same filter from its JSON export
    const importedFilter = BloomFilter.fromJSON(exported)
    console.log(importedFilter.has('alice')) // output: true
  3. What is an Invertible Bloom Lookup Table (IBLT)?

    master

    An Invertible Bloom Lookup Table (IBLT) is a space-efficient, probabilistic data structure used to solve the set-difference problem efficiently. It allows you to compute the difference between two sets (elements in set A but not B, and elements in set B but not A) with communication costs proportional to the size of the difference, rather than the size of the sets themselves.

    Key characteristics:

    • Set Reconciliation: It can simultaneously calculate $D(A \setminus B)$ and $D(B \setminus A)$ using $O(d)$ space, where $d$ is the number of differences.
    • XOR-based: It encodes sets by randomly combining elements using the XOR function.
    • Probabilistic: While highly efficient, it is a probabilistic structure used for reconciliation tasks.
  4. Use ScalableBloomFilter for dynamic capacity

    master

    A ScalableBloomFilter is a variant of a Bloom Filter that adapts dynamically to the number of elements stored while maintaining a maximum false positive probability. It achieves this by adding new PartitionBloomFilter instances as the current filter reaches its capacity (load factor >= 0.5).

    Key Characteristics

    • Dynamic Growth: Automatically adds new filters when the current one is full.
    • Error Rate Tightening: Each new filter added has a reduced error rate controlled by a _ratio to ensure the cumulative false positive probability remains bounded.
    • Capacity: The total capacity is the sum of the capacities of all internal filters.
    • False Positive Rate: The overall rate is the product of the rates of all internal filters.
    import ScalableBloomFilter from 'bloom-filters/scalable-bloom-filter';
    
    // Create a filter with initial size 128 and error rate 0.001
    const filter = new ScalableBloomFilter(128, 0.001);
    
    // Add elements
    filter.add('my-element');
    
    // Check for existence
    if (filter.has('my-element')) {
      console.log('Element found!');
    }
  5. Use XorFilter for high-performance static membership testing

    master

    The XorFilter is a high-performance, static membership filter designed for fixed sets of elements. It is highly space-efficient but, unlike standard Bloom Filters, it is not incremental. You must provide the entire set of elements at once to build the filter. Once built, it provides fast has() checks to determine if an element might be in the set.

    Key Constraints:

    • Static Only: You cannot add elements one by one after the filter is created. The add() method must be called with an array of elements exactly matching the size provided in the constructor.
    • Uniqueness: The input array to add() must contain unique values. Duplicates will cause an error.
    • Fingerprint Sizes: You can choose the fingerprint length (bits per fingerprint) to balance space efficiency and false positive rates. Supported sizes are 8, 16, 32, or 64 bits.
    import XorFilter from 'bloom-filters/xor-filter.js';
    
    // Option 1: Create and add elements manually
    let xor = new XorFilter(3, 16); // size=3, 16-bit fingerprints
    xor.add(['a', 'b', 'c']);
    console.log(xor.has('a')); // true
    console.log(xor.has('d')); // false
    
    // Option 2: Use the static create() method (Recommended)
    const xor = XorFilter.create(['a', 'b', 'c'], 16);
    console.log(xor.has('a')); // true
  6. How TopK works internally

    master

    The TopK implementation combines two data structures to achieve efficient tracking:

    1. Count-Min Sketch: A probabilistic data structure used to estimate the frequency (cardinality) of elements. It provides an upper bound on the true frequency with a configurable errorRate and accuracy.
    2. Min-Heap: A priority queue used to maintain a sliding window of the k highest-scoring elements. When a new element's estimated frequency exceeds the smallest frequency currently in the heap, the heap is updated to include the new element and potentially remove the previous minimum.

    This combination allows the TopK to track heavy hitters in a stream without storing every unique element encountered.

  7. Use MinHash to estimate Jaccard similarity

    master

    The MinHash class implements a locality-sensitive hashing scheme to estimate the Jaccard similarity coefficient between two large sets of numbers.

    Important: To get accurate results, you must only compare MinHash instances that were produced using the same MinHashFactory (or at least the same number of hash functions and identical HashFunction parameters). Comparing MinHashes with different hash functions will yield incorrect similarity estimates.

    Key Methods:

    • add(value: number): Inserts a single number into the MinHash and updates its signature.
    • bulkLoad(values: number[]): Efficiently ingests an array of numbers to update the signature.
    • compareWith(other: MinHash): Returns the estimated Jaccard similarity coefficient (a value between 0 and 1). Throws EmptyMinHashError if either MinHash is empty.
    import MinHash, { HashFunction } from 'bloom-filters';
    
    const hashFunctions: HashFunction[] = [
      { a: 1, b: 2, c: 101 },
      { a: 3, b: 4, c: 103 }
    ];
    
    const minhash1 = new MinHash(2, hashFunctions);
    minhash1.add(10);
    minhash1.add(20);
    
    const minhash2 = new MinHash(2, hashFunctions);
    minhash2.add(10);
    minhash2.add(30);
    
    const similarity = minhash1.compareWith(minhash2);
    console.log(`Estimated similarity: ${similarity}`);
  8. Check compatibility and migration requirements

    master

    When migrating between versions, be aware of breaking changes in hashing and indexing.

    Specifically, bug fixes introduced in versions 1.3.7 and between 1.3.9 and 2.0.0+ changed how data is hashed and indexed. If you are upgrading to these versions, you must re-build your filters completely from scratch to ensure compatibility. New major versions will be released whenever the hashing/indexing system is modified or the API breaks.

  9. Use the Counting Bloom Filter

    master

    A Counting Bloom filter tracks insertions and deletions by using small counters instead of single bits. This allows for the removal of elements.

    Methods:

    • add(element: HashableInput): Add an element.
    • remove(element: HashableInput): Delete an element (returns true if successful, false otherwise).
    • has(element: HashableInput): Test for membership.
    • equals(other: CountingBloomFilter): Compare equality.
    • rate(): Return the error rate.
    const CountingBloomFilter = require('bloom-filters').CountingBloomFilter
    
    // create a Bloom Filter with capacity = 15 and 4 hash functions
    let filter = new CountingBloomFilter(15, 4)
    
    // add some value in the filter
    filter.add('alice')
    filter.add('bob')
    filter.add('carole')
    
    // remove some value
    filter.remove('carole')
    
    // lookup for some data
    console.log(filter.has('bob')) // output: true
    console.log(filter.has('carole')) // output: false
    console.log(filter.has('daniel')) // output: false
    
    // print false positive rate (around 0.1)
    console.log(filter.rate())
    
    // alternatively, create a Counting Bloom Filter optimal for a number of items and a desired error rate
    const items = ['alice', 'bob']
    const errorRate = 0.04 // 4 % error rate
    filter = CountingBloomFilter.create(items.length, errorRate)
    
    // or create a Counting Bloom Filter optimal for a collections of items and a desired error rate
    filter = CountingBloomFilter.from(items, errorRate)
  10. Use the XOR Filter

    master

    A XOR Filter is a highly space-efficient probabilistic data structure, ideal for read-only sets. It is generally faster and smaller than both Bloom and Cuckoo filters.

    Implementation Details:

    • Supports 8-bit and 16-bit fingerprint lengths.
    • Uses Buffers internally, which are exported/imported as base64 strings.
    • Input Types: Accepts HashableInput or Long.

    Methods:

    • add(elements: XorHashableInput[]): Adds elements to the filter. Note: Calling this method more than once will override the current filter with the new elements.
    • has(element: XorHashableInput): Returns true if the element is in the set, false otherwise.
    const {XorFilter} = require('bloom-filters')
    const xor8 = new XorFilter(1)
    xor8.add(['a'])
    xor8.has('a') // true
    xor8.has('b') // false
    // or the combined
    const filter = XorFilter.create(['a'])
    filter.has('a') // true
    // using 16-bits fingerprint length
    XorFilter.create(['a'], 16).has('a') // true
    const a = new XorFilter(1, 16)
    a.add(['a'])
    a.has('a') // true
  11. Use the Scalable Bloom Filter

    master

    A Scalable Bloom Filter dynamically adapts to the number of elements stored while maintaining a maximum false positive probability. It uses Partitioned Bloom Filters internally.

    Methods:

    • add(element: HashableInput): Add an element.
    • has(element: HashableInput): Test for membership.
    • equals(other: ScalableBloomFilter): Compare equality.
    • capacity(): Return the total capacity.
    • rate(): Return the current error rate of the internal filter being used.
    const {ScalableBloomFilter} = require('bloom-filters')
    
    // by default it creates an ideally scalable bloom filter for 8 elements with an error rate of 0.01 and a load factor of 0.5
    const filter = new ScalableBloomFilter()
    filter.add('alice')
    filter.add('bob')
    filter.add('carl')
    for (let i = 0; i < 10000; i++) {
      filter.add('elem:' + i)
    }
    filter.has('somethingwrong') // false
    
    filter.capacity() // total capacity
    filter.rate() // current rate of the current internal filter used