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

org.roaringbitmap.bsi.buffer.MutableBitSliceIndex Maven / Gradle / Ivy

package org.roaringbitmap.bsi.buffer;

import java.util.Objects;
import org.roaringbitmap.RoaringBitmap;
import org.roaringbitmap.bsi.BitmapSliceIndex;
import org.roaringbitmap.bsi.Pair;
import org.roaringbitmap.bsi.WritableUtils;
import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
import org.roaringbitmap.buffer.MutableRoaringBitmap;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.OptionalInt;

/**
 * MutableBSI
 */
public class MutableBitSliceIndex extends BitSliceIndexBase implements BitmapSliceIndex {

  private boolean runOptimized;

  /**
   * construct a new ImmutableBitSliceIndex from raw slice
   *
   * @param maxValue max value for this bsi
   * @param minValue min value for this bsi
   * @param bA  bit slices for this bsi.using MutableRoaringBitmap array express
   * @param ebM exits value bitmap,use MutableRoaringBitmap express
   */
  public MutableBitSliceIndex(int maxValue, int minValue, 
      MutableRoaringBitmap[] bA, MutableRoaringBitmap ebM) {
    this.maxValue = maxValue;
    this.minValue = minValue;
    this.bA = bA;
    this.ebM = ebM;
  }


  /**
   * construct a new MutableBitSliceIndex.
   * Min/Max values are optional.  If set to 0 then the underlying BSI will be automatically sized.
   */
  public MutableBitSliceIndex(int minValue, int maxValue) {
    if (minValue < 0) {
      throw new IllegalArgumentException("Values should be non-negative");
    }

    this.bA = new MutableRoaringBitmap[32 - Integer.numberOfLeadingZeros(maxValue)];
    for (int i = 0; i < bA.length; i++) {
      this.bA[i] = new MutableRoaringBitmap();
    }
    this.ebM = new MutableRoaringBitmap();
  }

  /**
   * constructs an auto-sized BSI
   */
  public MutableBitSliceIndex() {
    this(0, 0);
  }

  private void ensureCapacityInternal(int minValue, int maxValue) {
    if (ebM.isEmpty()) {
      this.minValue = minValue;
      this.maxValue = maxValue;
      grow(Integer.toBinaryString(maxValue).length());
    } else if (this.minValue > minValue) {
      this.minValue = minValue;
    } else if (this.maxValue < maxValue) {
      this.maxValue = maxValue;
      grow(Integer.toBinaryString(maxValue).length());
    }
  }

  /**
   * auto expend the bA length.
   *
   * @param newBitDepth new bit depth
   */
  private void grow(int newBitDepth) {
    int oldBitDepth = this.bA.length;

    if (oldBitDepth >= newBitDepth) {
      return;
    }

    MutableRoaringBitmap[] newBA = new MutableRoaringBitmap[newBitDepth];

    if (oldBitDepth != 0) {
      System.arraycopy(this.bA, 0, newBA, 0, oldBitDepth);
    }

    for (int i = newBitDepth - 1; i >= oldBitDepth; i--) {
      newBA[i] = new MutableRoaringBitmap();
      if (this.runOptimized) {
        newBA[i].runOptimize();
      }
    }
    this.bA = newBA;
  }


  /**
   * RunOptimize attempts to further compress the runs of consecutive values found in the bitmap
   */
  public void runOptimize() {
    this.ebM.toMutableRoaringBitmap().runOptimize();

    for (int i = 0; i < this.bA.length; i++) {
      this.getMutableSlice(i).runOptimize();
    }
    this.runOptimized = true;
  }

  public boolean hasRunCompression() {
    return this.runOptimized;
  }

  public void addDigit(MutableRoaringBitmap foundSet, int i) {
    MutableRoaringBitmap carry = MutableRoaringBitmap.and(this.bA[i], foundSet);
    this.getMutableSlice(i).xor(foundSet);
    if (!carry.isEmpty()) {
      if (i + 1 >= this.bitCount()) {
        grow(this.bitCount() + 1);
      }
      this.addDigit(carry, i + 1);
    }
  }

  public MutableRoaringBitmap getExistenceBitmap() {
    return (MutableRoaringBitmap) this.ebM;
  }

  public MutableRoaringBitmap getMutableSlice(int i) {
    return (MutableRoaringBitmap) this.bA[i];
  }

  /**
   * SetValue sets a value for a given columnID.
   *
   * @param columnId columnID
   * @param value the value for columnID
   */
  public void setValue(int columnId, int value) {
    ensureCapacityInternal(value, value);
    setValueInternal(columnId, value);
  }

  private void setValueInternal(int columnId, int value) {
    for (int i = 0; i < this.bitCount(); i += 1) {
      if ((value & (1 << i)) > 0) {
        this.getMutableSlice(i).add(columnId);
      } else {
        this.getMutableSlice(i).remove(columnId);
      }
    }

    this.getExistenceBitmap().add(columnId);
  }


  public void setValues(List> values, 
      Integer currentMaxValue, Integer currentMinValue) {
    OptionalInt maxValue = currentMaxValue != null 
        ? OptionalInt.of(currentMaxValue) : values.stream().mapToInt(Pair::getRight).max();
    OptionalInt minValue = currentMinValue != null 
        ? OptionalInt.of(currentMinValue) : values.stream().mapToInt(Pair::getRight).min();

    if (!maxValue.isPresent() || !minValue.isPresent()) {
      throw new IllegalArgumentException("wrong input values list");
    }

    ensureCapacityInternal(minValue.getAsInt(), maxValue.getAsInt());
    for (Pair pair : values) {
      this.setValue(pair.getKey(), pair.getValue());
    }

  }

  /**
   * Set a batch of values.
   *
   * @param values
   */
  @Override
  public void setValues(List> values) {
    int maxValue = values.stream().mapToInt(Pair::getRight).filter(Objects::nonNull).max().getAsInt();
    int minValue = values.stream().mapToInt(Pair::getRight).filter(Objects::nonNull).min().getAsInt();
    ensureCapacityInternal(minValue, maxValue);
    for (Pair pair : values) {
      setValueInternal(pair.getKey(), pair.getValue());
    }
  }

  /**
   * add tow bsi index
   *
   */
  public void add(MutableBitSliceIndex otherBsi) {

    if (null == otherBsi || otherBsi.ebM.isEmpty()) {
      return;
    }

    this.getExistenceBitmap().or(otherBsi.getExistenceBitmap());
    if (otherBsi.bitCount() > this.bitCount()) {
      grow(otherBsi.bitCount());
    }

    for (int i = 0; i < otherBsi.bitCount(); i++) {
      this.addDigit(otherBsi.getMutableSlice(i), i);
    }

    // update min and max after adding
    this.minValue = minValue();
    this.maxValue = maxValue();
  }

  private int minValue() {
    if (ebM.isEmpty()) {
      return 0;
    }

    MutableRoaringBitmap minValuesId = getExistenceBitmap();
    for (int i = bA.length - 1; i >= 0; i -= 1) {
      MutableRoaringBitmap tmp = MutableRoaringBitmap.andNot(minValuesId, bA[i]);
      if (!tmp.isEmpty()) {
        minValuesId = tmp;
      }
    }

    return valueAt(minValuesId.first());
  }

  private int maxValue() {
    if (ebM.isEmpty()) {
      return 0;
    }

    MutableRoaringBitmap maxValuesId = getExistenceBitmap();
    for (int i = bA.length - 1; i >= 0; i -= 1) {
      MutableRoaringBitmap tmp = MutableRoaringBitmap.and(maxValuesId, bA[i]);
      if (!tmp.isEmpty()) {
        maxValuesId = tmp;
      }
    }

    return valueAt(maxValuesId.first());
  }

  private int valueAt(int columnId) {
    int value = 0;
    for (int i = 0; i < this.bitCount(); i += 1) {
      if (this.bA[i].contains(columnId)) {
        value |= (1 << i);
      }
    }

    return value;
  }

  /**
   * merge will merge 2 bsi into current
   * merge API was designed for distributed computing
   * NOTE: current and other bsi have no intersection
   *
   */
  public void merge(MutableBitSliceIndex otherBsi) {

    if (null == otherBsi || otherBsi.ebM.isEmpty()) {
      return;
    }

    // todo whether we need this
    if (MutableRoaringBitmap.intersects(this.ebM, otherBsi.ebM)) {
      throw new IllegalArgumentException("merge can be used only in bsiA  bsiB  is null");
    }

    int bitDepth = Integer.max(this.bitCount(), otherBsi.bitCount());
    MutableRoaringBitmap[] newBA = new MutableRoaringBitmap[bitDepth];
    for (int i = 0; i < bitDepth; i++) {
      MutableRoaringBitmap current = i < this.bA.length 
          ? this.getMutableSlice(i) : new MutableRoaringBitmap();
      MutableRoaringBitmap other = i < otherBsi.bA.length 
          ? otherBsi.getMutableSlice(i) : new MutableRoaringBitmap();
      newBA[i] = MutableRoaringBitmap.or(current, other);
      if (this.runOptimized || otherBsi.runOptimized) {
        newBA[i].runOptimize();
      }
    }
    this.bA = newBA;
    this.getExistenceBitmap().or(otherBsi.getExistenceBitmap());
    this.runOptimized = this.runOptimized || otherBsi.runOptimized;
    this.maxValue = Integer.max(this.maxValue, otherBsi.maxValue);
    this.minValue = Integer.min(this.minValue, otherBsi.minValue);
  }

  public MutableBitSliceIndex clone() {
    MutableBitSliceIndex bitSliceIndex = new MutableBitSliceIndex();
    bitSliceIndex.minValue = this.minValue;
    bitSliceIndex.maxValue = this.maxValue;
    bitSliceIndex.ebM = this.ebM.clone();
    MutableRoaringBitmap[] cloneBA = new MutableRoaringBitmap[this.bitCount()];
    for (int i = 0; i < cloneBA.length; i++) {
      cloneBA[i] = this.getMutableSlice(i).clone();
    }
    bitSliceIndex.bA = cloneBA;
    bitSliceIndex.runOptimized = this.runOptimized;

    return bitSliceIndex;

  }

  public void serialize(ByteBuffer buffer) {
    // write meta
    buffer.putInt(this.minValue);
    buffer.putInt(this.maxValue);
    buffer.put(this.runOptimized ? (byte) 1 : (byte) 0);
    // write ebm
    this.ebM.serialize(buffer);

    // write ba
    buffer.putInt(this.bA.length);
    for (ImmutableRoaringBitmap rb : this.bA) {
      rb.serialize(buffer);
    }
  }

  public void serialize(DataOutput output) throws IOException {
    // write meta
    WritableUtils.writeVInt(output, minValue);
    WritableUtils.writeVInt(output, maxValue);
    output.writeBoolean(this.runOptimized);

    // write ebm
    this.ebM.serialize(output);

    // write ba
    WritableUtils.writeVInt(output, this.bA.length);
    for (ImmutableRoaringBitmap rb : this.bA) {
      rb.serialize(output);
    }
  }

  private void clear() {
    this.maxValue = 0;
    this.minValue = 0;
    this.ebM = null;
    this.bA = null;
  }


  public void deserialize(ByteBuffer buffer) throws IOException {
    this.clear();
    // read meta
    this.minValue = buffer.getInt();
    this.maxValue = buffer.getInt();
    this.runOptimized = buffer.get() == (byte) 1;

    // read ebm
    MutableRoaringBitmap ebm = new MutableRoaringBitmap();
    ebm.deserialize(buffer);
    this.ebM = ebm;
    // read ba
    buffer.position(buffer.position() + ebm.serializedSizeInBytes());
    int bitDepth = buffer.getInt();
    MutableRoaringBitmap[] ba = new MutableRoaringBitmap[bitDepth];
    for (int i = 0; i < bitDepth; i++) {
      MutableRoaringBitmap rb = new MutableRoaringBitmap();
      rb.deserialize(buffer);
      ba[i] = rb;
      buffer.position(buffer.position() + rb.serializedSizeInBytes());
    }
    this.bA = ba;
  }

  public void deserialize(DataInput in) throws IOException {
    this.clear();

    // read meta
    this.minValue = WritableUtils.readVInt(in);
    this.maxValue = WritableUtils.readVInt(in);
    this.runOptimized = in.readBoolean();

    // read ebm
    MutableRoaringBitmap ebm = new MutableRoaringBitmap();
    ebm.deserialize(in);
    this.ebM = ebm;

    // read ba
    int bitDepth = WritableUtils.readVInt(in);
    MutableRoaringBitmap[] ba = new MutableRoaringBitmap[bitDepth];
    for (int i = 0; i < bitDepth; i++) {
      MutableRoaringBitmap rb = new MutableRoaringBitmap();
      rb.deserialize(in);
      ba[i] = rb;
    }
    this.bA = ba;
  }

  public int serializedSizeInBytes() {
    int size = 0;
    for (ImmutableRoaringBitmap rb : this.bA) {
      size += rb.serializedSizeInBytes();
    }
    return 4 + 4 + 1 + 4 + this.ebM.serializedSizeInBytes() + size;
   }

  public ImmutableBitSliceIndex toImmutableBitSliceIndex() {
    ImmutableRoaringBitmap[] ibA = new ImmutableRoaringBitmap[this.bA.length];
    for (int i = 0; i < this.bA.length; i++) {
      ibA[i] = this.bA[i];
    }

    ImmutableBitSliceIndex bsi = new ImmutableBitSliceIndex(
        this.maxValue, this.minValue, ibA, this.ebM);
    return bsi;
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy