Skip to content

Commit a4b59ca

Browse files
authored
Merge pull request #8 from Abductcows/generalise-shifts
Generalise shifts used in remove/add to work with multiple bits at a time
2 parents 9d7a77c + 8d40838 commit a4b59ca

File tree

3 files changed

+71
-67
lines changed

3 files changed

+71
-67
lines changed

src/main/java/gr/geompokon/bitarray/BitArray.java

Lines changed: 64 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -318,43 +318,41 @@ private void addAndShiftAllRight(boolean bit, int longIndex, int indexInLong) {
318318
int maxLongIndex = getLongIndex(elements);
319319
// add the bit and save the LSB that was shifted out
320320
int bitIntValue = Boolean.compare(bit, Boolean.FALSE);
321-
int rightmostBit = insertInLong(bitIntValue, longIndex++, indexInLong);
321+
long rightmostBit = insertInLong(bitIntValue, 1, longIndex++, indexInLong);
322322
// keep inserting old LSB at 0 of next long and moving on with the new LSB
323323
while (longIndex <= maxLongIndex) {
324-
rightmostBit = insertInLong(rightmostBit, longIndex++, 0);
324+
rightmostBit = insertInLong(rightmostBit, 1, longIndex++, 0);
325325
}
326326
}
327327

328328
/**
329-
* Inserts the bit in the index of the long specified by the arguments and returns the previous LSB.
329+
* Inserts the {@code lastLength} rightmost bits of lastValue in the position specified by {@code longIndex} and
330+
* {@code indexInLong}, and then shifts every element with index >= {@code indexInLong} to the right. The bits that
331+
* are shifted out are returned in the leftmost position
330332
*
331-
* <p>
332-
* Inserting at any index is done by splitting the long word in two parts and rejoining them after shifting and
333-
* setting the new bit. The LSB that is shifted out is returned.
334-
* </p>
335-
*
336-
* @param bit the bit to be inserted
333+
* @param lastValue bits to be inserted into the long
334+
* @param lastLength length in bits of the last value
337335
* @param longIndex index of the long in the {@code data} array
338-
* @param indexInLong index of the bit in the long
339-
* @return LSB of the long before insertion
336+
* @param indexInLong index of the insertion bit in the long
337+
* @return bits that were shifted out due to the insertion
340338
*/
341-
private int insertInLong(int bit, int longIndex, int indexInLong) {
342-
// get right side [indexInLong : ], can not be empty, will be shifted
339+
private long insertInLong(long lastValue, int lastLength, int longIndex, int indexInLong) {
340+
// select the bits [indexInLong, (word end)] for the insertion
343341
long rightSide = (data[longIndex] << indexInLong) >>> indexInLong;
344-
// get left side [0 : indexInLong), can be empty, will remain intact
345-
long leftSide = data[longIndex] - rightSide;
346-
347-
// save LSB
348-
long rightSideLSB = rightSide & 1L;
349-
// unsigned shift to the right to make space for the new bit
350-
rightSide >>>= 1;
351-
// set the new bit
352-
rightSide |= (long) bit << (BITS_PER_LONG - 1 - indexInLong);
342+
// separate the left part, this will remain intact
343+
long leftSide = data[longIndex] & ~rightSide;
344+
345+
// save the bits that will be shifted out
346+
long rightSideShiftOut = selectBits(rightSide, BITS_PER_LONG - lastLength, lastLength);
347+
// unsigned shift to the right to make space for the new bits
348+
rightSide >>>= lastLength;
349+
// set the new bits
350+
rightSide |= lastValue << (BITS_PER_LONG - lastLength - indexInLong);
353351
// re-join the two parts
354-
data[longIndex] = leftSide + rightSide;
352+
data[longIndex] = leftSide ^ rightSide;
355353

356-
// return the LSB
357-
return (int) rightSideLSB;
354+
// return the discarded bits
355+
return rightSideShiftOut;
358356
}
359357

360358
/**
@@ -366,47 +364,63 @@ private int insertInLong(int bit, int longIndex, int indexInLong) {
366364
private void removeAndShiftAllLeft(int longIndex, int indexInLong) {
367365
// start at the end and work back to current long index
368366
int currentLongIndex = getLongIndex(elements - 1);
369-
int leftmostBit = 0; // dud value for first shift
367+
long leftmostBit = 0; // dud value for first shift
370368
// keep adding the old MSB as LSB of the previous long index and shifting the rest to the left
371369
while (currentLongIndex > longIndex) {
372-
leftmostBit = appendBitAndRemoveAtIndex(leftmostBit, currentLongIndex--, 0);
370+
leftmostBit = removeAtIndexAndAppend(leftmostBit, 1, currentLongIndex--, 0);
373371
}
374-
// add the final MSB as LSB of {@code longIndex} and shift only the bits to the removed's right
375-
appendBitAndRemoveAtIndex(leftmostBit, longIndex, indexInLong);
372+
// add the final MSB as LSB of longIndex and shift only the bits to the popped bit's right
373+
removeAtIndexAndAppend(leftmostBit, 1, longIndex, indexInLong);
376374
}
377375

378376
/**
379-
* Appends the bit at the end of the long specified by the arguments and removes the bit at {@code indexInLong}.
380-
*
381-
* <p>
382-
* Since {@code indexInLong} can be at the middle of the long word, removing the bit is done by splitting the
383-
* long in two parts, clearing the desired bit and shifting once to restore the order of the previous bits.
384-
* </p>
377+
* Removes the {@code lastLength} bits from the long specified by {@code longIndex} starting from {@code indexInLong}
378+
* and then appends the same length of bits from {@code lastValue} at the end of the long. The
385379
*
386-
* @param bit the bit to be appended to the long
380+
* @param lastValue bits to be appended to the long
381+
* @param lastLength length in bits of the last value
387382
* @param longIndex index of the long in the {@code data} array
388-
* @param indexInLong index of the bit in the long
389-
* @return bit at {@code longIndex} that was popped out
383+
* @param indexInLong index of the first removed bit in the long
384+
* @return bits that were popped from the long
390385
*/
391-
private int appendBitAndRemoveAtIndex(int bit, int longIndex, int indexInLong) {
386+
private long removeAtIndexAndAppend(long lastValue, int lastLength, int longIndex, int indexInLong) {
392387
// get right side [indexInLong : ], can not be empty, will be shifted
393388
long rightSide = (data[longIndex] << indexInLong) >>> indexInLong;
394389
// get left side [0 : indexInLong), can be empty, will remain intact
395-
long leftSide = data[longIndex] - rightSide;
390+
long leftSide = data[longIndex] & ~rightSide;
391+
392+
// save removed values
393+
long poppedValues = selectBits(rightSide, indexInLong, lastLength) >>> (BITS_PER_LONG - indexInLong - lastLength);
396394

397-
// save MSB
398-
int rightSideMSB = getBitInLong(rightSide, indexInLong);
399-
// clear MSB and shift to the left to make it "disappear"
400-
rightSide &= ~singleBitMask(indexInLong);
401-
rightSide <<= 1;
402-
// append the previous bit
403-
rightSide += bit;
395+
// clear copied bits and shift to the left
396+
rightSide = (rightSide << indexInLong + lastLength) >>> indexInLong;
397+
// append the previous bits
398+
rightSide |= lastValue;
404399

405400
// re-join the two parts
406-
data[longIndex] = leftSide + rightSide;
401+
data[longIndex] = leftSide ^ rightSide;
402+
403+
// return the popped bits
404+
return poppedValues;
405+
}
407406

408-
// return the MSB
409-
return rightSideMSB;
407+
/**
408+
* Returns a long bit mask with ones only in the range [start, start + length)
409+
*
410+
* @param start start index of the selection
411+
* @param length number of set bits in the result
412+
* @return bit mask covering the range specified
413+
* @implSpec <p>
414+
* {@code start} should be in the range [0, 63]<br>
415+
* {@code length} should be in the range [1, 64]<br>
416+
* {@code start} and {@code length} should satisfy: start + length <= {@link #BITS_PER_LONG}
417+
* </p>
418+
*/
419+
private long selectBits(long aLong, int start, int length) {
420+
long mask = Long.MIN_VALUE >>> start; // need at least the first bit
421+
mask |= (Long.MIN_VALUE >>> start) - 1; // make everything to the right ones
422+
mask &= -(Long.MIN_VALUE >>> (start + length - 1)); // make everything from end of length and forward 0
423+
return aLong & mask;
410424
}
411425

412426
/**
@@ -503,7 +517,6 @@ private int longsRequiredForNBits(int nBits) {
503517
(double) nBits / BITS_PER_LONG);
504518
}
505519

506-
507520
/*
508521
BitArray specific methods
509522
*/

src/test/java/gr/geompokon/bitarray/BitArrayInterfaceTest.java

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ void addAtIndex() {
8888
@Test
8989
void set() {
9090
// test empty array behaviour
91-
assertThrows(Exception.class, () -> bitArray.set(0, true));
91+
assertThrows(IndexOutOfBoundsException.class, () -> bitArray.set(0, true));
9292

9393
// test with elements
9494
initArrays(MAX_TEST_SIZE);
@@ -109,23 +109,14 @@ void set() {
109109
@Test
110110
void remove() {
111111
// test empty array behaviour
112-
assertThrows(Exception.class, () -> bitArray.remove(0));
112+
assertThrows(IndexOutOfBoundsException.class, () -> bitArray.remove(0));
113113

114114
// test with elements
115115
initArrays(MAX_TEST_SIZE);
116-
117-
for (int i = 0; i < MAX_TEST_SIZE && !bitArray.isEmpty(); i++) {
118-
int index = random.nextInt(bitArray.size());
119-
bitArray.remove(index);
120-
boolArray.remove(index);
121-
}
122-
myAssertSameArrays();
123-
124-
while (!bitArray.isEmpty()) {
125-
bitArray.remove(bitArray.size() - 1);
126-
boolArray.remove(bitArray.size() - 1);
116+
for (int i = 0; i < MAX_TEST_SIZE; i++) {
117+
int index = random.nextInt(boolArray.size());
118+
assertEquals(boolArray.remove(index), bitArray.remove(index));
127119
}
128-
myAssertSameArrays();
129120
}
130121

131122
@Test

src/test/java/gr/geompokon/bitarray/BitArrayVsArrayListBenchmarkTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ void doRandomGet() {
142142
setUpBitArray();
143143
for (int i = 0; i < APPEND_SIZE; i++) {
144144
int getIndex = rand.nextInt(arraySize);
145-
boolean b = bitArray.get(getIndex);
145+
Boolean b = bitArray.get(getIndex);
146146
}
147147
stopTimerAndPrint();
148148
bitArray.clear(); // clear to free memory
@@ -151,7 +151,7 @@ void doRandomGet() {
151151
setUpBoolArray();
152152
for (int i = 0; i < APPEND_SIZE; i++) {
153153
int getIndex = rand.nextInt(arraySize);
154-
boolean b = boolArray.get(getIndex);
154+
Boolean b = boolArray.get(getIndex);
155155
}
156156
stopTimerAndPrint();
157157
}

0 commit comments

Comments
 (0)