All Downloads are FREE. Search and download functionalities are using the official Maven repository.

orestes.bloomfilter.memory.CountingBloomFilterMemory Maven / Gradle / Ivy

Go to download

Library of different Bloom filters in Java with optional Redis-backing, counting and many hashing options.

The newest version!
package orestes.bloomfilter.memory;

import orestes.bloomfilter.BloomFilter;
import orestes.bloomfilter.CountingBloomFilter;
import orestes.bloomfilter.FilterBuilder;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.BitSet;
import java.util.stream.IntStream;


public class CountingBloomFilterMemory implements CountingBloomFilter {
    private static final long serialVersionUID = -3207752201903871264L;
    protected FilterBuilder config;
    protected BloomFilterMemory filter;
    protected BitSet counts;
    protected transient Runnable overflowHandler = () -> { };

    protected CountingBloomFilterMemory() { }


    public CountingBloomFilterMemory(FilterBuilder config) {
        config.complete();
        this.config = config;
        this.filter = new BloomFilterMemory<>(config.clone());
        this.counts = new BitSet(config.size() * config().countingBits());
    }

    @Override
    public boolean contains(byte[] element) {
        return filter.contains(element);
    }


    @Override
    public synchronized long addAndEstimateCountRaw(byte[] element) {
        return IntStream.of(hash(element)).mapToLong(hash -> {
            filter.setBit(hash, true);
            return increment(hash);
        }).min().getAsLong();
    }


    @Override
    public synchronized long removeAndEstimateCountRaw(byte[] element) {
        if (!contains(element)) { return 0; }

        long min = Long.MAX_VALUE;
        for (int hash : hash(element)) {
            long count = decrement(hash);
            filter.setBit(hash, count > 0);
            min = (min >= count ? count : min);
        }

        return min;
    }


    /**
     * Increment the internal counter upon insertion of new elements.
     *
     * @param index position at which to increase
     * @return the new counter value
     */
    protected long increment(int index) {
        int low = index * config().countingBits();
        int high = (index + 1) * config().countingBits();

        // Do a binary +1 on the slice of length countingBits
        boolean incremented = false;
        long count = 0;
        int pos = 0;
        for (int i = (high - 1); i >= low; i--) {
            if (!counts.get(i) && !incremented) {
                counts.set(i);
                incremented = true;
            } else if (!incremented) {
                counts.set(i, false);
            }

            if (counts.get(i)) {
                count += Math.pow(2, pos);
            }
            pos++;
        }

        // If the counter overflowed, call the handler
        // and set the counter to the maximum value
        if (!incremented) {
            overflowHandler.run();
            for (int i = (high - 1); i >= low; i--) {
                counts.set(i);
            }
            //return max value
            count = (long) Math.pow(2, config().countingBits() - 1);
        }
        return count;
    }

    protected long count(int index) {
        int low = index * config().countingBits();
        int high = (index + 1) * config().countingBits();

        long count = 0;
        int pos = 0;
        //bit * 2^0 + bit * 2^1 ...
        for (int i = (high - 1); i >= low; i--) {
            if (counts.get(i)) {
                count += Math.pow(2, pos);
            }
            pos++;
        }
        return count;
    }

    /**
     * Decrements the internal counter upon deletion and unsets the Bloom filter bit if necessary.
     *
     * @param index position at which to decrease
     * @return the new counter value
     */
    protected long decrement(int index) {
        int low = index * config().countingBits();
        int high = (index + 1) * config().countingBits();

        // Do a binary -1 on the counter's slice of length countingBits
        boolean decremented = false;
        boolean nonZero = false;
        long count = 0;
        int pos = 0;
        for (int i = (high - 1); i >= low; i--) {
            if (!decremented) {
                // Flip every bit until you reach the first bit that is one. Flip that and stop flipping.
                if (counts.get(i)) {
                    counts.set(i, false);
                    decremented = true;
                } else {
                    counts.set(i, true);
                    nonZero = true;
                }
            } else {
                // While there is still one bit that is not zero the counter isn't zero
                if (counts.get(i)) {
                    nonZero = true;
                }
            }
            if (counts.get(i)) {
                count += Math.pow(2, pos);
            }
            pos++;
        }

        return count;
    }

    @Override
    public synchronized long getEstimatedCount(T element) {
        return IntStream.of(hash(toBytes(element))).mapToLong(this::count).min().getAsLong();
    }

    @Override
    public boolean union(BloomFilter other) {
        //TODO
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean intersect(BloomFilter other) {
        //TODO
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isEmpty() {
        return filter.isEmpty();
    }

    @Override
    public synchronized String toString() {
        StringBuilder sb = new StringBuilder(asString() + "\n");
        for (int i = 0; i < config().size(); i++) {
            sb.append(filter.getBit(i) ? 1 : 0);
            sb.append(" ");
            if (counts != null) {
                for (int j = 0; j < config().countingBits(); j++) {
                    sb.append(counts.get(config().countingBits() * i + j) ? 1 : 0);
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    @Override
    @SuppressWarnings("unchecked")
    public synchronized CountingBloomFilterMemory clone() {
        CountingBloomFilterMemory o = null;
        try {
            o = (CountingBloomFilterMemory) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        o.filter = (BloomFilterMemory) this.filter.clone();
        if (this.counts != null) { o.counts = (BitSet) this.counts.clone(); }
        o.config = this.config.clone();
        return o;
    }

    @Override
    public void clear() {
        filter.clear();
        counts.clear();
    }


    @Override
    public BitSet getBitSet() {
        return filter.getBitSet();
    }

    @Override
    public FilterBuilder config() {
        return this.config;
    }

    @Override
    public synchronized boolean equals(Object o) {
        if (this == o) { return true; }
        if (!(o instanceof CountingBloomFilterMemory)) { return false; }

        CountingBloomFilterMemory that = (CountingBloomFilterMemory) o;

        if (config != null ? !config.isCompatibleTo(that.config) : that.config != null) { return false; }
        if (counts != null ? !counts.equals(that.counts) : that.counts != null) { return false; }
        if (filter != null ? !filter.equals(that.filter) : that.filter != null) { return false; }

        return true;
    }


    public void setOverflowHandler(Runnable callback) {
        this.overflowHandler = callback;
    }

    private void readObject(ObjectInputStream stream)
        throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        this.overflowHandler = () -> {};
    }

    public BloomFilterMemory getBloomFilter() {
        return filter;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy