UNPKG

@rawify/bloomfilter

Version:

The RAW BloomFilter library, a fast, memory-efficient bloom filter implementation for membership checks.

93 lines (81 loc) 3.34 kB
/* eslint-env mocha */ const assert = require('assert').strict; const { BloomFilter } = require('@rawify/bloomfilter'); // Polyfill btoa/atob for Node if needed if (typeof globalThis.btoa !== 'function') { globalThis.btoa = (str) => Buffer.from(str, 'binary').toString('base64'); } if (typeof globalThis.atob !== 'function') { globalThis.atob = (b64) => Buffer.from(b64, 'base64').toString('binary'); } describe('BloomFilter', () => { it('constructs from capacity and errorRate', () => { const bf = new BloomFilter({ capacity: 10000, errorRate: 0.01 }); assert.ok(bf.bitCount > 0); assert.ok(bf.hashCount > 0); assert.ok(bf.bitset instanceof Uint32Array); }); it('constructs from bitCount and hashCount', () => { const bf = new BloomFilter({ bitCount: 1 << 15, hashCount: 5 }); assert.equal(bf.bitCount, 1 << 15); assert.equal(bf.hashCount, 5); }); it('adds and finds elements (no false negatives)', () => { const bf = new BloomFilter({ capacity: 1000, errorRate: 0.01 }); bf.add('alice'); bf.addAll(['bob', 'carol']); assert.equal(bf.mightContain('alice'), true); assert.equal(bf.mightContain('bob'), true); assert.equal(bf.mightContain('carol'), true); assert.equal(bf.mightContain('mallory'), false); // definitely not present }); it('handles binary inputs', () => { const bf = new BloomFilter({ capacity: 500, errorRate: 0.01 }); const bytes = new Uint8Array([1, 2, 3, 4]); bf.add(bytes); assert.equal(bf.mightContain(bytes), true); }); it('clear() resets the filter', () => { const bf = new BloomFilter({ capacity: 100, errorRate: 0.01 }); bf.add('x'); assert.equal(bf.mightContain('x'), true); bf.clear(); assert.equal(bf.mightContain('x'), false); assert.equal(bf.countSetBits(), 0); }); it('estimates cardinality and FP rate reasonably', () => { const n = 200, p = 0.01; const bf = new BloomFilter({ capacity: n, errorRate: p }); for (let i = 0; i < n; i++) bf.add(`k${i}`); const est = bf.estimatedCardinality(); assert.ok(est > n * 0.5 && est < n * 2); const fp = bf.estimatedFalsePositiveRate(); assert.ok(fp > 0 && fp < 0.2); }); it('union and intersection work correctly', () => { const bf1 = new BloomFilter({ capacity: 100, errorRate: 0.01 }); const bf2 = new BloomFilter({ capacity: 100, errorRate: 0.01 }); bf1.add('foo'); bf2.add('bar'); bf1.add('both'); bf2.add('both'); const uni = BloomFilter.union(bf1, bf2); assert.equal(uni.mightContain('foo'), true); assert.equal(uni.mightContain('bar'), true); assert.equal(uni.mightContain('both'), true); const inter = BloomFilter.intersection(bf1, bf2); assert.equal(inter.mightContain('both'), true); // Should almost certainly exclude exclusive items assert.equal(inter.mightContain('foo'), false); assert.equal(inter.mightContain('bar'), false); }); it('serializes and restores with toJSON/fromJSON', () => { const bf = new BloomFilter({ capacity: 100, errorRate: 0.01 }); bf.addAll(['alice', 'bob']); const dump = bf.toJSON(); const bf2 = BloomFilter.fromJSON(dump); assert.equal(bf2.mightContain('alice'), true); assert.equal(bf2.mightContain('bob'), true); assert.equal(bf2.mightContain('carol'), false); }); });