| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.wayang.core.util; |
| |
| import java.util.NoSuchElementException; |
| import java.util.PrimitiveIterator; |
| import java.util.Spliterator; |
| import java.util.Spliterators; |
| import java.util.stream.IntStream; |
| import java.util.stream.StreamSupport; |
| |
| /** |
| * A mutable bit-mask. |
| */ |
| public class Bitmask implements Cloneable, Iterable<Integer> { |
| |
| /** |
| * An instance without any bits set. |
| */ |
| public static Bitmask EMPTY_BITMASK = new Bitmask(0); |
| |
| private static int BITS_PER_WORD = Long.BYTES * 8; |
| |
| private static int WORD_ADDRESS_BITS = 6; |
| |
| /** |
| * Stores the actual bits. |
| */ |
| private long[] bits; |
| |
| /** |
| * Caches the cardinality or is {@code -1} if no values is cached. |
| */ |
| private int cardinalityCache; |
| |
| /** |
| * Creates a new instance. |
| */ |
| public Bitmask() { |
| this(BITS_PER_WORD); |
| } |
| |
| /** |
| * Creates a new instance. |
| * |
| * @param startCapacity the number of bits (starting from {@code 0}) that should already be held available |
| */ |
| public Bitmask(int startCapacity) { |
| int numLongs = startCapacity > 0 ? getLongPos(startCapacity - 1) + 1 : 0; |
| this.bits = new long[numLongs]; |
| this.cardinalityCache = 0; |
| } |
| |
| /** |
| * Creates a new, copied instance. |
| * |
| * @param that the instance to copy |
| */ |
| public Bitmask(Bitmask that) { |
| this(that, that.bits.length << WORD_ADDRESS_BITS); |
| } |
| |
| /** |
| * Creates a new, copied instance. |
| * |
| * @param that the instance to copy |
| * @param startCapacity the number of bits (starting from {@code 0}) that should already be held available |
| */ |
| public Bitmask(Bitmask that, int startCapacity) { |
| this(Math.max(that.bits.length << WORD_ADDRESS_BITS, startCapacity)); |
| System.arraycopy(that.bits, 0, this.bits, 0, that.bits.length); |
| this.cardinalityCache = that.cardinalityCache; |
| } |
| |
| /** |
| * Find the appropriate index to {@link #bits} for a given index. |
| * |
| * @param index of a bit |
| * @return the index into {@link #bits} |
| */ |
| private static int getLongPos(int index) { |
| return index >>> WORD_ADDRESS_BITS; |
| } |
| |
| /** |
| * Find the appropriate offset in the appropriate element of {@link #bits} for a given index. |
| * |
| * @param index of a bit |
| * @return the offset |
| */ |
| private static int getOffset(int index) { |
| return index & (BITS_PER_WORD - 1); |
| } |
| |
| /** |
| * Sets the bit at the given index. |
| * |
| * @param index where the bit should be set |
| * @return whether this instance was changed |
| */ |
| public boolean set(int index) { |
| if (!this.ensureCapacity(index) && this.get(index)) { |
| return false; |
| } |
| final int longPos = getLongPos(index); |
| final int offset = getOffset(index); |
| this.bits[longPos] = this.bits[longPos] | (1L << offset); |
| if (this.cardinalityCache != -1) this.cardinalityCache++; |
| return true; |
| } |
| |
| /** |
| * Gets the bit at the given index. |
| * |
| * @param index where the bit should be set |
| * @return whether the bit in question is set |
| */ |
| public boolean get(int index) { |
| final int longPos = getLongPos(index); |
| if (longPos >= this.bits.length) return false; |
| final int offset = getOffset(index); |
| return ((this.bits[longPos] >>> offset) & 1) != 0; |
| } |
| |
| /** |
| * Makes sure that {@link #bits} is large enough to comprise the given {@code index}. |
| * |
| * @param index an index for a bit |
| * @return whether {@link #bits} was enlarged |
| */ |
| private boolean ensureCapacity(int index) { |
| int numRequiredLongs = getLongPos(index) + 1; |
| if (this.bits.length < numRequiredLongs) { |
| long[] newBits = new long[numRequiredLongs]; |
| System.arraycopy(this.bits, 0, newBits, 0, this.bits.length); |
| this.bits = newBits; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Retrieves the number of set bits in this instance. |
| * |
| * @return the number of set bits |
| */ |
| public int cardinality() { |
| if (this.cardinalityCache == -1) { |
| this.cardinalityCache = 0; |
| for (long bits : this.bits) { |
| this.cardinalityCache += Long.bitCount(bits); |
| } |
| } |
| return this.cardinalityCache; |
| } |
| |
| /** |
| * Tells whether this instance is empty. |
| * |
| * @return whether this instance is empty |
| */ |
| public boolean isEmpty() { |
| return this.cardinality() == 0; |
| } |
| |
| /** |
| * Accumulates the given instance to this one via logical OR. |
| * |
| * @param that that should be accumulated |
| * @return this instance |
| */ |
| public Bitmask orInPlace(Bitmask that) { |
| this.ensureCapacity(that.bits.length << WORD_ADDRESS_BITS); |
| for (int i = 0; i < that.bits.length; i++) { |
| this.bits[i] |= that.bits[i]; |
| } |
| this.cardinalityCache = -1; |
| return this; |
| } |
| |
| /** |
| * Creates the new instance that merges this and the given one via logical OR. |
| * |
| * @param that the other instance |
| * @return this merged instance |
| */ |
| public Bitmask or(Bitmask that) { |
| Bitmask copy = new Bitmask(this, that.bits.length << WORD_ADDRESS_BITS); |
| return copy.orInPlace(that); |
| } |
| |
| |
| /** |
| * Accumulates the given instance to this one via logical AND. |
| * |
| * @param that that should be accumulated |
| * @return this instance |
| */ |
| public Bitmask andInPlace(Bitmask that) { |
| this.ensureCapacity(that.bits.length << WORD_ADDRESS_BITS); |
| for (int i = 0; i < that.bits.length; i++) { |
| this.bits[i] &= that.bits[i]; |
| } |
| for (int i = that.bits.length; i < this.bits.length; i++) { |
| this.bits[i] = 0; |
| } |
| this.cardinalityCache = -1; |
| return this; |
| } |
| |
| /** |
| * Creates the new instance that merges this and the given one via logical AND. |
| * |
| * @param that the other instance |
| * @return this merged instance |
| */ |
| public Bitmask and(Bitmask that) { |
| Bitmask copy = new Bitmask(this, that.bits.length << WORD_ADDRESS_BITS); |
| return copy.andInPlace(that); |
| } |
| |
| /** |
| * Accumulates the given instance to this one via logical AND NOT. |
| * |
| * @param that that should be accumulated |
| * @return this instance |
| */ |
| public Bitmask andNotInPlace(Bitmask that) { |
| this.ensureCapacity(that.bits.length << WORD_ADDRESS_BITS); |
| for (int i = 0; i < that.bits.length; i++) { |
| this.bits[i] &= ~that.bits[i]; |
| } |
| this.cardinalityCache = -1; |
| return this; |
| } |
| |
| /** |
| * Creates the new instance that merges this and the given one via logical AND NOT. |
| * |
| * @param that the other instance |
| * @return this merged instance |
| */ |
| public Bitmask andNot(Bitmask that) { |
| Bitmask copy = new Bitmask(this, that.bits.length << WORD_ADDRESS_BITS); |
| return copy.andNotInPlace(that); |
| } |
| |
| /** |
| * Flips all bits in the given range |
| * |
| * @param fromIndex inclusive start index of the range |
| * @param toIndex exclusive end index of the range |
| * @return this instance |
| */ |
| public Bitmask flip(int fromIndex, int toIndex) { |
| if (fromIndex < toIndex) { |
| this.ensureCapacity(toIndex - 1); |
| int fromLongPos = getLongPos(fromIndex); |
| int untilLongPos = getLongPos(toIndex - 1); |
| for (int longPos = fromLongPos; longPos <= untilLongPos; longPos++) { |
| int fromOffset = (longPos == fromLongPos) ? getOffset(fromIndex) : 0; |
| int untilOffset = (longPos == untilLongPos) ? getOffset(toIndex - 1) + 1 : BITS_PER_WORD; |
| long flipMask = createAllSetBits(untilOffset) ^ createAllSetBits(fromOffset); |
| this.bits[longPos] ^= flipMask; |
| } |
| this.cardinalityCache = -1; |
| } |
| return this; |
| } |
| |
| /** |
| * Creates a {@code long} that has the lowest {@code n} bits set. |
| * |
| * @param n the number of bits to be set |
| * @return the {@code long} |
| */ |
| private static long createAllSetBits(int n) { |
| return n == BITS_PER_WORD ? -1L : (1L << n) - 1L; |
| } |
| |
| /** |
| * Checks whether all bits set in this instance are also set in the given instance. |
| * |
| * @param that the potential supermask |
| * @return whether this is a submask |
| */ |
| public boolean isSubmaskOf(Bitmask that) { |
| final int minBitsLength = Math.min(this.bits.length, that.bits.length); |
| for (int i = 0; i < minBitsLength; i++) { |
| if (this.bits[i] != (this.bits[i] & that.bits[i])) return false; |
| } |
| for (int i = minBitsLength; i < this.bits.length; i++) { |
| if (this.bits[i] != 0L) return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Checks whether all bits set in this instance are not set in the given instance. |
| * |
| * @param that the potential disjoint instance |
| * @return whether the instances are disjoint |
| */ |
| public boolean isDisjointFrom(Bitmask that) { |
| final int minBitsLength = Math.min(this.bits.length, that.bits.length); |
| for (int i = 0; i < minBitsLength; i++) { |
| if ((this.bits[i] & that.bits[i]) != 0) return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Finds the next set bit, starting from a given index. |
| * |
| * @param from index from that the search starts |
| * @return the index of the set next bit or {@code -1} if there is no next set bit |
| */ |
| public int nextSetBit(int from) { |
| int longPos = getLongPos(from); |
| int offset = getOffset(from); |
| while (longPos < this.bits.length) { |
| long bits = this.bits[longPos]; |
| int nextOffset = Long.numberOfTrailingZeros(bits & ~createAllSetBits(offset)); |
| if (nextOffset < BITS_PER_WORD) { |
| return longPos << WORD_ADDRESS_BITS | nextOffset; |
| } |
| longPos++; |
| offset = 0; |
| } |
| return -1; |
| } |
| |
| @Override |
| public PrimitiveIterator.OfInt iterator() { |
| return new PrimitiveIterator.OfInt() { |
| |
| private int next = Bitmask.this.nextSetBit(0); |
| |
| @Override |
| public boolean hasNext() { |
| return this.next != -1; |
| } |
| |
| @Override |
| public int nextInt() { |
| if (!this.hasNext()) throw new NoSuchElementException(); |
| int result = this.next; |
| this.next = Bitmask.this.nextSetBit(this.next + 1); |
| return result; |
| } |
| |
| @Override |
| public Integer next() { |
| return this.nextInt(); |
| } |
| |
| }; |
| } |
| |
| @Override |
| public Spliterator.OfInt spliterator() { |
| return Spliterators.spliteratorUnknownSize(this.iterator(), 0); |
| } |
| |
| public IntStream stream() { |
| return StreamSupport.intStream(this.spliterator(), false); |
| } |
| |
| @Override |
| public String toString() { |
| return this.toIndexString(); |
| } |
| |
| @SuppressWarnings("unused") |
| private String toHexString() { |
| StringBuilder sb = new StringBuilder(2 + this.bits.length * 8); |
| sb.append("0x"); |
| for (long bits : this.bits) { |
| sb.append(String.format("%016x", bits)); |
| } |
| return sb.toString(); |
| } |
| |
| @SuppressWarnings("unused") |
| private String toBinString() { |
| StringBuilder sb = new StringBuilder(2 + this.bits.length * 64); |
| sb.append("["); |
| for (long bits : this.bits) { |
| for (int i = 0; i < Long.BYTES * 8; i++) { |
| sb.append(((bits >> i) & 1L) == 0 ? "0" : "1"); |
| } |
| } |
| return sb.append(']').toString(); |
| } |
| |
| private String toIndexString() { |
| StringBuilder sb = new StringBuilder(2 + this.cardinality() * 8); |
| sb.append("{"); |
| String separator = ""; |
| int nextIndex = this.nextSetBit(0); |
| while (nextIndex != -1) { |
| sb.append(separator).append(nextIndex); |
| separator = ", "; |
| nextIndex = this.nextSetBit(nextIndex + 1); |
| } |
| sb.append("}"); |
| return sb.toString(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| Bitmask that = (Bitmask) o; |
| if (this.cardinalityCache != -1 && that.cardinalityCache != -1 && this.cardinalityCache != that.cardinalityCache) { |
| return false; |
| } |
| final Bitmask smallInstance; |
| final Bitmask largeInstance; |
| if (this.bits.length < that.bits.length) { |
| smallInstance = this; |
| largeInstance = that; |
| } else { |
| smallInstance = that; |
| largeInstance = this; |
| } |
| for (int i = 0; i < smallInstance.bits.length; i++) { |
| if (smallInstance.bits[i] != largeInstance.bits[i]) return false; |
| } |
| for (int i = smallInstance.bits.length; i < largeInstance.bits.length; i++) { |
| if (largeInstance.bits[i] != 0L) return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int accu = 0; |
| for (long bits : this.bits) { |
| accu ^= bits ^ (bits >>> 32); |
| } |
| return accu; |
| } |
| } |