/*
* Joinery -- Data frames for Java
* Copyright (c) 2014, 2015 IBM Corp.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package joinery.impl;
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.Arrays;
/**
* A sparse bit set implementation inspired by Drs. Haddon and Lemire.
*
* https://github.com/brettwooldridge/SparseBitSet/blob/master/SparseBitSet.pdf
* http://lemire.me/blog/archives/2012/11/13/fast-sets-of-integers/
*/
public class SparseBitSet {
//
// these are the tuning knobs
//
// after taking out the bits for indexing longs,
// how to divide up the remaining bits among the levels
// larger numbers means levels 2 and 3 are smaller
// leaving more bits to be indexed in level 1
private static final int INDEX_FACTOR = 4;
// how much bigger is the second level than the first
// (the third is just whatever bits are leftover)
private static final int INDEX_GROWTH = 1;
// l3 constants
// v-- number of bits to index a long value
private static final int L3_SHIFT = Long.SIZE - 1 - Long.numberOfLeadingZeros(Long.SIZE);
// v-- divide remaining bits by factor
private static final int L3_BITS = (Integer.SIZE - L3_SHIFT) / INDEX_FACTOR;
private static final int L3_SIZE = (1 << L3_BITS);
private static final int L3_MASK = L3_SIZE - 1;
// l2 constants
private static final int L2_SHIFT = L3_SHIFT + L3_BITS;
private static final int L2_BITS = L3_BITS + INDEX_GROWTH;
private static final int L2_SIZE = (1 << L2_BITS);
private static final int L2_MASK = L2_SIZE - 1;
// l1 constants
// 32 bits - index size - 1 more prevent shifting the sign bit
// into frame
private static final int L1_SHIFT = L2_SHIFT + L2_BITS;
private static final int L1_BITS = (Integer.SIZE - L1_SHIFT - 1);
private static final int L1_SIZE = (1 << L1_BITS);
// note the l1 mask is one more bit left than size would indicate
// this prevents masking the sign bit, causing the appropriate
// index out of bounds exception if a negative index is used
private static final int L1_MASK = (L1_SIZE << 1) - 1;
// l4 mask
private static final int L4_MASK = Long.SIZE - 1;
long[][][] bits = new long[L3_SIZE / INDEX_FACTOR][][];
int cardinality = 0;
public boolean get(final int index) {
final int l1i = (index >> L1_SHIFT) & L1_MASK,
l2i = (index >> L2_SHIFT) & L2_MASK,
l3i = (index >> L3_SHIFT) & L3_MASK,
l4i = (index ) & L4_MASK;
// index < 0 allowed through so appropriate index out of bounds exception is thrown
if (index < 0 || l1i < bits.length && (bits[l1i] != null && bits[l1i][l2i] != null)) {
return (bits[l1i][l2i][l3i] & (1L << l4i)) != 0L;
}
return false;
}
public void set(final int index, final boolean value) {
final int l1i = (index >> L1_SHIFT) & L1_MASK,
l2i = (index >> L2_SHIFT) & L2_MASK,
l3i = (index >> L3_SHIFT) & L3_MASK,
l4i = (index ) & L4_MASK;
if (value) {
if (bits.length <= l1i && l1i < L1_SIZE) {
final int size = min(L1_SIZE, max(bits.length << 1,
1 << (Integer.SIZE - Integer.numberOfLeadingZeros(l1i))));
if (bits.length < size) {
bits = Arrays.copyOf(bits, size);
}
}
if (bits[l1i] == null) {
bits[l1i] = new long[L2_SIZE][];
}
if (bits[l1i][l2i] == null) {
bits[l1i][l2i] = new long[L3_SIZE];
}
bits[l1i][l2i][l3i] |= (1L << l4i);
cardinality++;
} else {
// don't allocate blocks if clearing bits
if (l1i < bits.length && bits[l1i] != null && bits[l1i][l2i] != null) {
bits[l1i][l2i][l3i] &= ~(1L << l4i);
cardinality--;
}
}
}
public void set(final int index) {
set(index, true);
}
public void set(final int start, final int end) {
for (int i = start; i < end; i++) {
set(i);
}
}
public void clear(final int index) {
set(index, false);
}
public void clear(final int start, final int end) {
for (int i = start; i < end; i++) {
clear(i);
}
}
public void flip(final int index) {
set(index, !get(index));
}
public void flip(final int start, final int end) {
for (int i = start; i < end; i++) {
flip(i);
}
}
public void clear() {
Arrays.fill(bits, null);
cardinality = 0;
}
public int cardinality() {
return cardinality;
}
public int nextSetBit(final int index) {
int l1i = (index >> L1_SHIFT) & L1_MASK,
l2i = (index >> L2_SHIFT) & L2_MASK,
l3i = (index >> L3_SHIFT) & L3_MASK,
l4i = (index ) & L4_MASK;
for ( ; l1i < bits.length; l1i++, l2i = 0) {
for ( ; bits[l1i] != null && l2i < bits[l1i].length; l2i++, l3i = 0) {
for ( ; bits[l1i][l2i] != null && l3i < bits[l1i][l2i].length; l3i++, l4i = 0) {
l4i += Long.numberOfTrailingZeros(bits[l1i][l2i][l3i] >> l4i);
if ((bits[l1i][l2i][l3i] & (1L << l4i)) != 0L) {
return (l1i << L1_SHIFT) |
(l2i << L2_SHIFT) |
(l3i << L3_SHIFT) |
l4i;
}
}
}
}
return -1;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("{");
for (int i = nextSetBit(0); i >= 0; i = nextSetBit(i + 1)) {
if (sb.length() > 1) {
sb.append(", ");
}
sb.append(i);
}
sb.append("}");
return sb.toString();
}
public static String parameters() {
final StringBuilder sb = new StringBuilder();
sb.append(String.format("%s parameters:\n", SparseBitSet.class.getName()))
.append(String.format("size:\tlevel 1=%d\tlevel 2=%d\tlevel 3=%d\n", L1_SIZE, L2_SIZE, L3_SIZE))
.append(String.format("bits:\tlevel 1=%d\tlevel 2=%d\tlevel 3=%d\n", L1_BITS, L2_BITS, L3_BITS))
.append(String.format("shift:\tlevel 1=%d\tlevel 2=%d\tlevel 3=%d\n", L1_SHIFT, L2_SHIFT, L3_SHIFT))
.append(String.format("mask:\tlevel 1=%s\tlevel 2=%s\tlevel 3=%s\n",
Integer.toHexString(L1_MASK), Integer.toHexString(L2_MASK), Integer.toHexString(L3_MASK)));
return sb.toString();
}
}