Skip to content

Commit fe53e18

Browse files
authored
Merge pull request #634 from jimmywarting/feature/modernize-bitset
Feature/modernize bitset
2 parents b183cd8 + 8f4fec6 commit fe53e18

File tree

2 files changed

+121
-93
lines changed

2 files changed

+121
-93
lines changed

lib/bitset.js

Lines changed: 99 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,102 +2,129 @@
22
//
33
//
44

5-
// A bitset implementation, after that in java.util. Yes there
6-
// already exist such things, but none implement next{Clear|Set}Bit or
7-
// equivalent, and none involved me tooling about for an evening.
8-
95
'use strict';
106

11-
function BitSet(size) {
12-
if (size) {
13-
var numWords = Math.ceil(size / 32);
14-
this.words = new Array(numWords);
7+
/**
8+
* A bitset implementation, after that in java.util. Yes there
9+
* already exist such things, but none implement next{Clear|Set}Bit or
10+
* equivalent, and none involved me tooling about for an evening.
11+
*/
12+
class BitSet {
13+
/**
14+
* @param {number} [size]
15+
*/
16+
constructor(size) {
17+
if (size) {
18+
const numWords = Math.ceil(size / 32);
19+
this.words = new Array(numWords);
20+
}
21+
else {
22+
this.words = [];
23+
}
24+
this.wordsInUse = 0; // = number, not index
1525
}
16-
else {
17-
this.words = [];
26+
27+
/**
28+
* @param {number} numWords
29+
*/
30+
ensureSize(numWords) {
31+
const wordsPresent = this.words.length;
32+
if (wordsPresent < numWords) {
33+
this.words = this.words.concat(new Array(numWords - wordsPresent));
34+
}
1835
}
19-
this.wordsInUse = 0; // = number, not index
20-
}
2136

22-
var P = BitSet.prototype;
37+
/**
38+
* @param {number} bitIndex
39+
*/
40+
set(bitIndex) {
41+
const w = wordIndex(bitIndex);
42+
if (w >= this.wordsInUse) {
43+
this.ensureSize(w + 1);
44+
this.wordsInUse = w + 1;
45+
}
46+
const bit = 1 << bitIndex;
47+
this.words[w] |= bit;
48+
}
2349

24-
function wordIndex(bitIndex) {
25-
return Math.floor(bitIndex / 32);
26-
}
50+
/**
51+
* @param {number} bitIndex
52+
*/
53+
clear(bitIndex) {
54+
const w = wordIndex(bitIndex);
55+
if (w >= this.wordsInUse) return;
56+
const mask = ~(1 << bitIndex);
57+
this.words[w] &= mask;
58+
}
2759

28-
// Make sure we have at least numWords
29-
P.ensureSize = function(numWords) {
30-
var wordsPresent = this.words.length;
31-
if (wordsPresent < numWords) {
32-
this.words = this.words.concat(new Array(numWords - wordsPresent));
60+
/**
61+
* @param {number} bitIndex
62+
*/
63+
get(bitIndex) {
64+
const w = wordIndex(bitIndex);
65+
if (w >= this.wordsInUse) return false; // >= since index vs size
66+
const bit = 1 << bitIndex;
67+
return !!(this.words[w] & bit);
3368
}
34-
}
3569

36-
P.set = function(bitIndex) {
37-
var w = wordIndex(bitIndex);
38-
if (w >= this.wordsInUse) {
39-
this.ensureSize(w + 1);
40-
this.wordsInUse = w + 1;
70+
/**
71+
* Give the next bit that is set on or after fromIndex, or -1 if no such bit
72+
*
73+
* @param {number} fromIndex
74+
*/
75+
nextSetBit(fromIndex) {
76+
let w = wordIndex(fromIndex);
77+
if (w >= this.wordsInUse) return -1;
78+
79+
// the right-hand side is shifted to only test the bits of the first
80+
// word that are > fromIndex
81+
let word = this.words[w] & (0xffffffff << fromIndex);
82+
while (true) {
83+
if (word) return (w * 32) + trailingZeros(word);
84+
w++;
85+
if (w === this.wordsInUse) return -1;
86+
word = this.words[w];
87+
}
4188
}
42-
var bit = 1 << bitIndex;
43-
this.words[w] |= bit;
44-
};
4589

46-
P.clear = function(bitIndex) {
47-
var w = wordIndex(bitIndex);
48-
if (w >= this.wordsInUse) return;
49-
var mask = ~(1 << bitIndex);
50-
this.words[w] &= mask;
51-
};
90+
/**
91+
* @param {number} fromIndex
92+
*/
93+
nextClearBit(fromIndex) {
94+
let w = wordIndex(fromIndex);
95+
if (w >= this.wordsInUse) return fromIndex;
5296

53-
P.get = function(bitIndex) {
54-
var w = wordIndex(bitIndex);
55-
if (w >= this.wordsInUse) return false; // >= since index vs size
56-
var bit = 1 << bitIndex;
57-
return !!(this.words[w] & bit);
97+
let word = ~(this.words[w]) & (0xffffffff << fromIndex);
98+
while (true) {
99+
if (word) return (w * 32) + trailingZeros(word);
100+
w++;
101+
if (w == this.wordsInUse) return w * 32;
102+
word = ~(this.words[w]);
103+
}
104+
}
58105
}
59106

107+
/**
108+
* @param {number} bitIndex
109+
*/
110+
function wordIndex(bitIndex) {
111+
return Math.floor(bitIndex / 32);
112+
}
113+
114+
/**
115+
* @param {number} i
116+
*/
60117
function trailingZeros(i) {
61118
// From Hacker's Delight, via JDK. Probably far less effective here,
62119
// since bit ops are not necessarily the quick way to do things in
63120
// JS.
64121
if (i === 0) return 32;
65-
var y, n = 31;
122+
let y, n = 31;
66123
y = i << 16; if (y != 0) { n = n -16; i = y; }
67124
y = i << 8; if (y != 0) { n = n - 8; i = y; }
68125
y = i << 4; if (y != 0) { n = n - 4; i = y; }
69126
y = i << 2; if (y != 0) { n = n - 2; i = y; }
70127
return n - ((i << 1) >>> 31);
71128
}
72129

73-
// Give the next bit that's set on or after fromIndex, or -1 if no such
74-
// bit
75-
P.nextSetBit = function(fromIndex) {
76-
var w = wordIndex(fromIndex);
77-
if (w >= this.wordsInUse) return -1;
78-
79-
// the right-hand side is shifted to only test the bits of the first
80-
// word that are > fromIndex
81-
var word = this.words[w] & (0xffffffff << fromIndex);
82-
while (true) {
83-
if (word) return (w * 32) + trailingZeros(word);
84-
w++;
85-
if (w === this.wordsInUse) return -1;
86-
word = this.words[w];
87-
}
88-
};
89-
90-
P.nextClearBit = function(fromIndex) {
91-
var w = wordIndex(fromIndex);
92-
if (w >= this.wordsInUse) return fromIndex;
93-
94-
var word = ~(this.words[w]) & (0xffffffff << fromIndex);
95-
while (true) {
96-
if (word) return (w * 32) + trailingZeros(word);
97-
w++;
98-
if (w == this.wordsInUse) return w * 32;
99-
word = ~(this.words[w]);
100-
}
101-
};
102-
103130
module.exports.BitSet = BitSet;

test/bitset.js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,69 @@
11
'use strict';
22

3-
var claire = require('claire');
3+
const claire = require('claire');
4+
const {BitSet} = require('../lib/bitset');
45

5-
var forAll = claire.forAll,
6-
arb = claire.data,
7-
label = claire.label,
8-
choice = claire.choice,
9-
transform = claire.transform;
6+
const {
7+
forAll,
8+
data: arb,
9+
label,
10+
choice,
11+
transform
12+
} = claire;
1013

11-
var BitSet = require('../lib/bitset').BitSet;
12-
var PosInt = transform(Math.floor, arb.Positive);
14+
const PosInt = transform(Math.floor, arb.Positive);
1315

14-
var EmptyBitSet = label('bitset', transform(
15-
function(size) {
16+
const EmptyBitSet = label('bitset', transform(
17+
size => {
1618
return new BitSet(size);
1719
},
1820
choice(arb.Nothing, PosInt)));
1921

20-
suite('BitSet', function() {
22+
suite('BitSet', () => {
2123

2224
test('get bit', forAll(EmptyBitSet, PosInt)
23-
.satisfy(function(b, bit) {
25+
.satisfy((b, bit) => {
2426
b.set(bit);
2527
return b.get(bit);
2628
}).asTest());
27-
29+
2830
test('clear bit', forAll(EmptyBitSet, PosInt)
29-
.satisfy(function(b, bit) {
31+
.satisfy((b, bit) => {
3032
b.set(bit);
3133
b.clear(bit);
3234
return !b.get(bit);
3335
}).asTest());
3436

3537
test('next set of empty', forAll(EmptyBitSet)
36-
.satisfy(function(b) {
38+
.satisfy(b => {
3739
return b.nextSetBit(0) === -1;
3840
}).asTest());
3941

4042
test('next set of one bit', forAll(EmptyBitSet, PosInt)
41-
.satisfy(function(b, bit) {
43+
.satisfy((b, bit) => {
4244
b.set(bit);
4345
return b.nextSetBit(0) === bit;
4446
}).asTest());
4547

4648
test('next set same bit', forAll(EmptyBitSet, PosInt)
47-
.satisfy(function(b, bit) {
49+
.satisfy((b, bit) => {
4850
b.set(bit);
4951
return b.nextSetBit(bit) === bit;
5052
}).asTest());
5153

5254
test('next set following bit', forAll(EmptyBitSet, PosInt)
53-
.satisfy(function(b, bit) {
55+
.satisfy((b, bit) => {
5456
b.set(bit);
5557
return b.nextSetBit(bit+1) === -1;
5658
}).asTest());
5759

5860
test('next clear of empty', forAll(EmptyBitSet, PosInt)
59-
.satisfy(function(b, bit) { return b.nextClearBit(bit) === bit; })
61+
.satisfy((b, bit) => { return b.nextClearBit(bit) === bit; })
6062
.asTest());
6163

6264
test('next clear of one set', forAll(EmptyBitSet, PosInt)
63-
.satisfy(function(b, bit) {
65+
.satisfy((b, bit) => {
6466
b.set(bit);
6567
return b.nextClearBit(bit) === bit + 1;
6668
}).asTest());
67-
6869
});

0 commit comments

Comments
 (0)