Skip to content

Commit c5aff93

Browse files
authored
feat(rank): Implement the Rank API (#20)
Implement Rank(uint64) API which returns the rank of a given value in the whole sorted set.
1 parent bc614dc commit c5aff93

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

bitmap.go

+61
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,9 @@ func (ra *Bitmap) Select(x uint64) (uint64, error) {
464464
}
465465

466466
func (ra *Bitmap) Contains(x uint64) bool {
467+
if ra == nil {
468+
return false
469+
}
467470
key := x & mask
468471
offset, has := ra.keys.getValue(key)
469472
if !has {
@@ -484,6 +487,9 @@ func (ra *Bitmap) Contains(x uint64) bool {
484487
}
485488

486489
func (ra *Bitmap) Remove(x uint64) bool {
490+
if ra == nil {
491+
return false
492+
}
487493
key := x & mask
488494
offset, has := ra.keys.getValue(key)
489495
if !has {
@@ -563,6 +569,17 @@ func (ra *Bitmap) RemoveRange(lo, hi uint64) {
563569
ra.Cleanup()
564570
}
565571

572+
func (ra *Bitmap) Reset() {
573+
// reset ra.data to size enough for one container and corresponding key.
574+
// 2 u64 is needed for header and another 2 u16 for the key 0.
575+
ra.data = ra.data[:16+minContainerSize]
576+
ra.keys = toUint64Slice(ra.data)
577+
578+
offset := ra.newContainer(minContainerSize)
579+
ra.keys.setAt(indexNodeStart+1, offset)
580+
ra.keys.setNumKeys(1)
581+
}
582+
566583
func (ra *Bitmap) GetCardinality() int {
567584
if ra == nil {
568585
return 0
@@ -715,6 +732,11 @@ func (ra *Bitmap) extreme(dir int) uint64 {
715732
}
716733

717734
func (ra *Bitmap) And(bm *Bitmap) {
735+
if bm == nil {
736+
ra.Reset()
737+
return
738+
}
739+
718740
a, b := ra, bm
719741
ai, an := 0, a.keys.numKeys()
720742
bi, bn := 0, b.keys.numKeys()
@@ -787,6 +809,9 @@ func And(a, b *Bitmap) *Bitmap {
787809
}
788810

789811
func (ra *Bitmap) AndNot(bm *Bitmap) {
812+
if bm == nil {
813+
return
814+
}
790815
a, b := ra, bm
791816
var ai, bi int
792817

@@ -839,6 +864,9 @@ func (ra *Bitmap) AndNot(bm *Bitmap) {
839864

840865
// TODO: Check if we want to use lazyMode
841866
func (dst *Bitmap) Or(src *Bitmap) {
867+
if src == nil {
868+
return
869+
}
842870
dst.or(src, runInline)
843871
}
844872

@@ -926,6 +954,39 @@ func Or(a, b *Bitmap) *Bitmap {
926954
return res
927955
}
928956

957+
func (ra *Bitmap) Rank(x uint64) int {
958+
key := x & mask
959+
offset, has := ra.keys.getValue(key)
960+
if !has {
961+
return -1
962+
}
963+
c := ra.getContainer(offset)
964+
y := uint16(x)
965+
966+
// Find the rank within the container
967+
var rank int
968+
switch c[indexType] {
969+
case typeArray:
970+
rank = array(c).rank(y)
971+
case typeBitmap:
972+
rank = bitmap(c).rank(y)
973+
}
974+
if rank < 0 {
975+
return -1
976+
}
977+
978+
// Add up cardinalities of all the containers on the left of container containing x.
979+
n := ra.keys.numKeys()
980+
for i := 0; i < n; i++ {
981+
if ra.keys.key(i) == key {
982+
break
983+
}
984+
cont := ra.getContainer(ra.keys.val(i))
985+
rank += getCardinality(cont)
986+
}
987+
return rank
988+
}
989+
929990
func (ra *Bitmap) Cleanup() {
930991
for idx := 1; idx < ra.keys.numKeys(); {
931992
off := ra.keys.val(idx)

bitmap_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -654,5 +654,38 @@ func TestCleanup(t *testing.T) {
654654
for i := 65536; i < n; i++ {
655655
require.Falsef(t, a.Contains(uint64(i)), "idx: %d", i)
656656
}
657+
}
658+
659+
func TestRank(t *testing.T) {
660+
a := NewBitmap()
661+
n := int(1e6)
662+
for i := uint64(0); i < uint64(n); i++ {
663+
a.Set(i)
664+
}
665+
for i := 0; i < n; i++ {
666+
require.Equal(t, i, a.Rank(uint64(i)))
667+
}
668+
require.Equal(t, -1, a.Rank(uint64(n)))
669+
670+
// Check ranks after removing an element.
671+
a.Remove(100)
672+
for i := 0; i < n; i++ {
673+
if i < 100 {
674+
require.Equal(t, i, a.Rank(uint64(i)))
675+
} else if i == 100 {
676+
require.Equal(t, -1, a.Rank(uint64(i)))
677+
} else {
678+
require.Equal(t, i-1, a.Rank(uint64(i)))
679+
}
680+
}
657681

682+
// Check ranks after removing a range of elements.
683+
a.RemoveRange(0, uint64(1e4))
684+
for i := 0; i < n; i++ {
685+
if i < 1e4 {
686+
require.Equal(t, -1, a.Rank(uint64(n)))
687+
} else {
688+
require.Equal(t, i-1e4, a.Rank(uint64(i)))
689+
}
690+
}
658691
}

container.go

+30-1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ func (c array) find(x uint16) int {
123123
}
124124
return N
125125
}
126+
127+
func (c array) rank(x uint16) int {
128+
N := getCardinality(c)
129+
idx := c.find(x)
130+
if idx == N {
131+
return -1
132+
}
133+
return idx
134+
}
135+
126136
func (c array) has(x uint16) bool {
127137
N := getCardinality(c)
128138
idx := c.find(x)
@@ -441,13 +451,32 @@ func (b bitmap) has(x uint16) bool {
441451
return has > 0
442452
}
443453

454+
func (b bitmap) rank(x uint16) int {
455+
idx := x >> 4
456+
pos := x & 0xF
457+
if b[startIdx+idx]&bitmapMask[pos] == 0 {
458+
return -1
459+
}
460+
461+
var rank int
462+
for i := 0; i < int(idx); i++ {
463+
rank += bits.OnesCount16(b[int(startIdx)+i])
464+
}
465+
for p := uint16(0); p <= pos; p++ {
466+
if b[startIdx+idx]&bitmapMask[p] > 0 {
467+
rank++
468+
}
469+
}
470+
return rank - 1
471+
}
472+
444473
// TODO: This can perhaps be using SIMD instructions.
445474
func (b bitmap) andBitmap(other bitmap) []uint16 {
446475
out := make([]uint16, maxContainerSize)
447476
out[indexSize] = maxContainerSize
448477
out[indexType] = typeBitmap
449478
var num int
450-
for i := 4; i < len(b); i++ {
479+
for i := int(startIdx); i < len(b); i++ {
451480
out[i] = b[i] & other[i]
452481
num += bits.OnesCount16(out[i])
453482
}

0 commit comments

Comments
 (0)