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

org.roaringbitmap.FastRankRoaringBitmap Maven / Gradle / Ivy

Go to download

Roaring bitmaps are compressed bitmaps (also called bitsets) which tend to outperform conventional compressed bitmaps such as WAH or Concise.

There is a newer version: 1.3.0
Show newest version
package org.roaringbitmap;

import java.util.Arrays;

/**
 * This extends {@link RoaringBitmap} to provide better performance for .rank and .select
 * operations, at the cost of maintain a cache of cardinalities.
 * 
 * On {@link RoaringBitmap#select(int)} and {@link RoaringBitmap#rank(int)} operations,
 * {@link RoaringBitmap} needs to iterate along all underlying buckets to cumulate their
 * cardinalities. This may lead to sub-optimal performance for application doing a large amount of
 * .rank/.select over read-only {@link RoaringBitmap}, especially if the {@link RoaringBitmap} holds
 * a large number of underlying buckets.
 * 
 * This implementation will discard the cache of cardinality on any write operations, and it will
 * memoize the computed cardinalities on any .rank or .select operation
 * 
 * @author Benoit Lacelle
 *
 */
public class FastRankRoaringBitmap extends RoaringBitmap {
  // The cache of cardinalities: it maps the index of the underlying bucket to the cumulated
  // cardinalities (i.e. the sum of current bucket cardinalities plus all previous bucklet
  // cardinalities)
  private boolean cumulatedCardinalitiesCacheIsValid = false;
  private int[] highToCumulatedCardinality = null;

  public FastRankRoaringBitmap() {
    super();
  }

  public FastRankRoaringBitmap(RoaringArray array) {
    super(array);
  }

  private void resetCache() {
    // Reset the cache on any write operation
    cumulatedCardinalitiesCacheIsValid = false;
  }

  // VisibleForTesting
  boolean isCacheDismissed() {
    return !cumulatedCardinalitiesCacheIsValid;
  }

  @Override
  public void add(long rangeStart, long rangeEnd) {
    resetCache();
    super.add(rangeStart, rangeEnd);
  }

  @Override
  public void add(int x) {
    resetCache();
    super.add(x);
  }

  @Override
  public void add(int... dat) {
    resetCache();
    super.add(dat);
  }

  @Deprecated
  @Override
  public void add(int rangeStart, int rangeEnd) {
    resetCache();
    super.add(rangeStart, rangeEnd);
  }

  @Override
  public void clear() {
    resetCache();
    super.clear();
  }

  @Override
  public void flip(int x) {
    resetCache();
    super.flip(x);
  }

  @Deprecated
  @Override
  public void flip(int rangeStart, int rangeEnd) {
    resetCache();
    super.flip(rangeStart, rangeEnd);
  }

  @Override
  public void flip(long rangeStart, long rangeEnd) {
    resetCache();
    super.flip(rangeStart, rangeEnd);
  }

  @Override
  public void and(RoaringBitmap x2) {
    resetCache();
    super.and(x2);
  }

  @Override
  public void andNot(RoaringBitmap x2) {
    resetCache();
    super.andNot(x2);
  }

  @Deprecated
  @Override
  public void remove(int rangeStart, int rangeEnd) {
    resetCache();
    super.remove(rangeStart, rangeEnd);
  }

  @Override
  public void remove(int x) {
    resetCache();
    super.remove(x);
  }

  @Override
  public void remove(long rangeStart, long rangeEnd) {
    resetCache();
    super.remove(rangeStart, rangeEnd);
  }

  @Override
  public boolean checkedAdd(int x) {
    resetCache();
    return super.checkedAdd(x);
  }

  @Override
  public boolean checkedRemove(int x) {
    resetCache();
    return super.checkedRemove(x);
  }

  @Override
  public void or(RoaringBitmap x2) {
    resetCache();
    super.or(x2);
  }

  @Override
  public void xor(RoaringBitmap x2) {
    resetCache();
    super.xor(x2);
  }

  /**
   * On any .rank or .select operation, we pre-compute all cumulated cardinalities. It will enable
   * using a binary-search to spot the relevant underlying bucket. We may prefer to cache
   * cardinality only up-to the selected rank, but it would lead to a more complex implementation
   */
  private void preComputeCardinalities() {
    if (!cumulatedCardinalitiesCacheIsValid) {
      int nbBuckets = highLowContainer.size();

      // Ensure the cache size is the right one
      if (highToCumulatedCardinality == null || highToCumulatedCardinality.length != nbBuckets) {
        highToCumulatedCardinality = new int[nbBuckets];
      }

      // Ensure the cache content is valid
      if (highToCumulatedCardinality.length >= 1) {
        // As we compute the cumulated cardinalities, the first index is an edge case
        highToCumulatedCardinality[0] = highLowContainer.getContainerAtIndex(0).getCardinality();

        for (int i = 1; i < highToCumulatedCardinality.length; i++) {
          highToCumulatedCardinality[i] = highToCumulatedCardinality[i - 1]
              + highLowContainer.getContainerAtIndex(i).getCardinality();
        }
      }

      cumulatedCardinalitiesCacheIsValid = true;
    }
  }

  @Override
  public long rankLong(int x) {
    preComputeCardinalities();

    if (highLowContainer.size() == 0) {
      return 0L;
    }

    char xhigh = Util.highbits(x);

    int index = Util.hybridUnsignedBinarySearch(this.highLowContainer.keys, 0,
        this.highLowContainer.size(), xhigh);

    boolean hasBitmapOnIdex;
    if (index < 0) {
      hasBitmapOnIdex = false;
      index = -1 - index;
    } else {
      hasBitmapOnIdex = true;
    }

    long size = 0;
    if (index > 0) {
      size += highToCumulatedCardinality[index - 1];
    }

    long rank = size;
    if (hasBitmapOnIdex) {
      rank = size + this.highLowContainer.getContainerAtIndex(index).rank(Util.lowbits(x));
    }

    return rank;
  }

  @Override
  public int select(int j) {
    preComputeCardinalities();

    if (highLowContainer.size() == 0) {
      // empty: .select is out-of-bounds

      throw new IllegalArgumentException(
          "select " + j + " when the cardinality is " + this.getCardinality());
    }

    int maxCardinality = highToCumulatedCardinality[highToCumulatedCardinality.length - 1] - 1;
    if (j == maxCardinality) {
      // We select the total cardinality: we are selecting the last element of the last bucket
      return this.last();
    } else if (j > maxCardinality) {
      throw new IllegalArgumentException(
              "select " + j + " when the cardinality is " + this.getCardinality());
    }

    int index = Arrays.binarySearch(highToCumulatedCardinality, j);

    int fixedIndex;

    long leftover = Util.toUnsignedLong(j);

    if (index >= 0) {
      // We selected exactly a cumulated cardinality:
      // we are selecting the first element of next bucket
      int keycontrib = this.highLowContainer.getKeyAtIndex(index + 1) << 16;

      // If first bucket has cardinality 1 and we select 1: we actual select the first item of
      // next bucket
      int output = keycontrib + this.highLowContainer.getContainerAtIndex(index + 1).first();

      return output;
    } else {
      // We selected a cardinality not matching exactly the cumulated cardinalities: we are not
      // selected the last element of a bucket
      fixedIndex = -1 - index;
      if (fixedIndex > 0) {
        leftover -= highToCumulatedCardinality[fixedIndex - 1];
      }
    }

    int keycontrib = this.highLowContainer.getKeyAtIndex(fixedIndex) << 16;
    int lowcontrib = (
        this.highLowContainer.getContainerAtIndex(fixedIndex).select((int) leftover));
    int value = lowcontrib + keycontrib;


    return value;
  }
  
  @Override
  public long getLongSizeInBytes() {
    long size = 8;
    size += super.getLongSizeInBytes();
    if (highToCumulatedCardinality != null) {
      size += 4L * highToCumulatedCardinality.length;
    }
    return size;
  }

  /**
   * Get a special iterator that allows .peekNextRank efficiently
   *
   * @return iterator with fast rank access
   */
  public PeekableIntRankIterator getIntRankIterator() {
    preComputeCardinalities();
    return new FastRoaringIntRankIterator();
  }

  private class FastRoaringIntRankIterator implements PeekableIntRankIterator {
    private int hs = 0;

    private PeekableCharRankIterator iter;

    private int pos = 0;

    private FastRoaringIntRankIterator() {
      nextContainer();
    }

    @Override
    public int peekNextRank() {
      int iterRank = iter.peekNextRank();
      if (pos > 0) {
        return FastRankRoaringBitmap.this.highToCumulatedCardinality[pos - 1] + iterRank;
      } else {
        return iterRank;
      }
    }

    @Override
    public PeekableIntRankIterator clone() {
      try {
        FastRoaringIntRankIterator x =
            (FastRoaringIntRankIterator) super.clone();
        if (this.iter != null) {
          x.iter = this.iter.clone();
        }
        return x;
      } catch (CloneNotSupportedException e) {
        return null;// will not happen
      }
    }

    @Override
    public boolean hasNext() {
      return pos < FastRankRoaringBitmap.this.highLowContainer.size();
    }

    @Override
    public int next() {
      final int x = iter.nextAsInt() | hs;
      if (!iter.hasNext()) {
        ++pos;
        nextContainer();
      }
      return x;
    }

    private void nextContainer() {
      if (pos < FastRankRoaringBitmap.this.highLowContainer.size()) {
        iter = FastRankRoaringBitmap.this.highLowContainer.getContainerAtIndex(pos)
                                                          .getCharRankIterator();
        hs = FastRankRoaringBitmap.this.highLowContainer.getKeyAtIndex(pos) << 16;
      }
    }

    @Override
    public void advanceIfNeeded(int minval) {
      while (hasNext() && ((hs >>> 16) < (minval >>> 16))) {
        ++pos;
        nextContainer();
      }
      if (hasNext() && ((hs >>> 16) == (minval >>> 16))) {
        iter.advanceIfNeeded(Util.lowbits(minval));
        if (!iter.hasNext()) {
          ++pos;
          nextContainer();
        }
      }
    }

    @Override
    public int peekNext() {
      return (iter.peekNext()) | hs;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy