Skip to content

Commit

Permalink
Bitlist and bitlist64 full compatibility (#42)
Browse files Browse the repository at this point in the history
* Add ToBitlist() and ToBitlist64() conversion methods

* double conversion check

* extend NewBitlist64FromBytes()

* update ToBitlist64()

* remove redundant test

* fix UpdateBitlist()

* fix tests
  • Loading branch information
farazdagi authored Feb 2, 2021
1 parent 0db5713 commit 7fcea7c
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 208 deletions.
2 changes: 1 addition & 1 deletion bitlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (b Bitlist) BytesNoTrim() []byte {

// ToBitlist64 converts []byte backed bitlist into []uint64 backed bitlist.
func (b Bitlist) ToBitlist64() *Bitlist64 {
return NewBitlist64FromBytes(b.BytesNoTrim())
return NewBitlist64FromBytes(b.Len(), b.BytesNoTrim())
}

// Count returns the number of 1s in the bitlist.
Expand Down
31 changes: 24 additions & 7 deletions bitlist64.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bitfield

import (
"encoding/binary"
"fmt"
"math/bits"
)

Expand All @@ -27,7 +28,7 @@ type Bitlist64 struct {
data []uint64
}

// NewBitlist64 creates a new bitlist of size N.
// NewBitlist64 creates a new bitlist of size `n`.
func NewBitlist64(n uint64) *Bitlist64 {
return &Bitlist64{
size: n,
Expand All @@ -44,13 +45,17 @@ func NewBitlist64From(data []uint64) *Bitlist64 {
}

// NewBitlist64FromBytes creates a new bitlist for a given array of bytes.
func NewBitlist64FromBytes(b []byte) *Bitlist64 {
// Size of the bitlist is explicitly specified via `n` (since number of bits required may not align
// perfectly to the word size).
func NewBitlist64FromBytes(n uint64, b []byte) *Bitlist64 {
if n > uint64(len(b)<<3) {
panic(fmt.Sprintf("an array of %d bytes is not enough to hold n=%d bits.", len(b), n))
}
// Extend input slice with zero bytes if it isn't evenly divisible by word size.
if numExtraBytes := len(b) % bytesInWord; numExtraBytes != 0 {
b = append(b, make([]byte, bytesInWord-numExtraBytes)...)
}

n := uint64(len(b) << bytesInWordLog2)
data := make([]uint64, numWordsRequired(n))
for i := 0; i < len(data); i++ {
idx := i << bytesInWordLog2
Expand Down Expand Up @@ -131,8 +136,8 @@ func (b *Bitlist64) Bytes() []byte {

// ToBitlist converts []uint64 backed bitlist into []byte backed bitlist.
func (b *Bitlist64) ToBitlist() Bitlist {
if len(b.data) == 0 {
return Bitlist{}
if len(b.data) == 0 || b.size == 0 {
return NewBitlist(0)
}

ret := make([]byte, len(b.data)*bytesInWord)
Expand All @@ -141,8 +146,20 @@ func (b *Bitlist64) ToBitlist() Bitlist {
binary.LittleEndian.PutUint64(ret[start:start+bytesInWord], word)
}

// Append size byte when returning.
return append(ret, 0x1)
// Append size bit. If number of bits align evenly with number byte size (8), add extra byte.
// Otherwise, set the most significant bit as a length bit.
if b.size%8 == 0 {
// Limit number of bits to a known size. Add extra byte, with size bit set.
ret = append(ret[:b.size>>3], 0x1)
} else {
// Limit number of bits to a known size.
ret = ret[:b.size>>3+1]
// Set size bit on the last byte.
idx := uint8(1 << (b.size % 8))
ret[len(ret)-1] |= idx
}

return ret
}

// Count returns the number of 1s in the bitlist.
Expand Down
154 changes: 95 additions & 59 deletions bitlist64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func TestBitlist64_NewBitlist64(t *testing.T) {
size: 128,
want: &Bitlist64{size: 128, data: []uint64{0x00, 0x00}},
},
{
size: 129,
want: &Bitlist64{size: 129, data: []uint64{0x00, 0x00, 0x00}},
},
{
size: 256,
want: &Bitlist64{size: 256, data: []uint64{0x00, 0x00, 0x00, 0x00}},
Expand Down Expand Up @@ -206,50 +210,71 @@ func TestBitlist64_NewBitlist64From(t *testing.T) {
func TestBitlist64_NewBitlist64FromBytes(t *testing.T) {
tests := []struct {
from []byte
size uint64
want *Bitlist64
}{
{
from: []byte{},
want: &Bitlist64{size: 0, data: []uint64{}},
},
{
from: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
want: &Bitlist64{size: 64, data: []uint64{0x0000000000000000}},
from: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
size: 64,
want: &Bitlist64{size: 64, data: []uint64{0x0000000000000001}},
},
{
from: []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
size: 63,
want: &Bitlist64{size: 63, data: []uint64{0x0000000000000002}},
},
{
from: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
size: 64,
want: &Bitlist64{size: 64, data: []uint64{0xFFFFFFFFFFFFFFFF}},
},
{
from: []byte{
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01,
},
want: &Bitlist64{size: 128, data: []uint64{0x02, 0x01}},
size: 70,
want: &Bitlist64{size: 70, data: []uint64{0x02, 0x01}},
},
{
from: []byte{
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01,
},
size: 72,
want: &Bitlist64{size: 72, data: []uint64{0x02, 0x01}},
},
{
from: []byte{0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0},
size: 64,
want: &Bitlist64{size: 64, data: []uint64{0xF0DEBC9A78563412}},
},
{
from: []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
size: 128,
want: &Bitlist64{size: 128, data: []uint64{0x00, 0x00}},
},
{
from: []byte{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
},
size: 128,
want: &Bitlist64{size: 128, data: []uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},
},
{
from: []byte{
0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
},
size: 128,
want: &Bitlist64{size: 128, data: []uint64{0xFFFFFFFFFFFFFFF1, 0xFFFFFFFFFFFFFFF2}},
},
{
Expand All @@ -258,7 +283,8 @@ func TestBitlist64_NewBitlist64FromBytes(t *testing.T) {
0xF2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4,
},
want: &Bitlist64{size: 192, data: []uint64{
size: 184,
want: &Bitlist64{size: 184, data: []uint64{
0xFFFFFFFFFFFFFFF1,
0xFFFFFFFFFFFFFFF2,
0x00F4FFFFFFFFFFF3,
Expand All @@ -267,92 +293,102 @@ func TestBitlist64_NewBitlist64FromBytes(t *testing.T) {
}

for _, tt := range tests {
t.Run(fmt.Sprintf("data:%#x", tt.from), func(t *testing.T) {
got := NewBitlist64FromBytes(tt.from)
t.Run(fmt.Sprintf("NewBitlist64FromBytes(%d, %#x)", tt.size, tt.from), func(t *testing.T) {
got := NewBitlist64FromBytes(tt.size, tt.from)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewBitlist64FromBytes(%#x) = %+v, wanted %+v", tt.from, got, tt.want)
t.Errorf("NewBitlist64FromBytes(%d, %#x) = %+v, wanted %+v", tt.size, tt.from, got, tt.want)
}
})
}
}

func TestBitlist64_ToBitlist(t *testing.T) {
tests := []struct {
from *Bitlist64
want Bitlist
size uint64
selectedIndices []uint64
}{
{
from: &Bitlist64{size: 0, data: []uint64{}},
want: Bitlist{},
size: 0,
selectedIndices: []uint64{},
},
{
from: &Bitlist64{size: 64, data: []uint64{0x0000000000000000}},
want: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
size: 1,
selectedIndices: []uint64{0},
},
{
from: &Bitlist64{size: 64, data: []uint64{0xFFFFFFFFFFFFFFFF}},
want: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01},
size: 2,
selectedIndices: []uint64{0, 1},
},
{
from: &Bitlist64{size: 128, data: []uint64{0x02, 0x01}},
want: []byte{
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01,
},
size: 7,
selectedIndices: []uint64{0, 1, 6},
},
{
from: &Bitlist64{size: 64, data: []uint64{0xF0DEBC9A78563412}},
want: []byte{0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x01},
size: 8,
selectedIndices: []uint64{0, 1, 6, 7},
},
{
from: &Bitlist64{size: 128, data: []uint64{0x00, 0x00}},
want: []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01,
},
size: 9,
selectedIndices: []uint64{3, 4},
},
{
from: &Bitlist64{size: 128, data: []uint64{0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF}},
want: []byte{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x01,
},
size: 60,
selectedIndices: []uint64{0, 2, 50},
},
{
from: &Bitlist64{size: 128, data: []uint64{0xFFFFFFFFFFFFFFF1, 0xFFFFFFFFFFFFFFF2}},
want: []byte{
0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x01,
},
size: 64,
selectedIndices: []uint64{0, 2, 63},
},
{
from: &Bitlist64{size: 192, data: []uint64{
0xFFFFFFFFFFFFFFF1,
0xFFFFFFFFFFFFFFF2,
0x00F4FFFFFFFFFFF3,
}},
want: []byte{
0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00,
0x01,
},
size: 69,
selectedIndices: []uint64{0, 2, 63, 67},
},
{
size: 128,
selectedIndices: []uint64{0, 2, 63, 67, 120},
},
{
size: 128,
selectedIndices: []uint64{0, 2, 63, 67, 90, 100, 120, 126, 127},
},
{
size: 192,
selectedIndices: []uint64{0, 2, 63, 67, 90, 100, 120, 126, 127, 150, 170},
},
}

selectIndices := func(b Bitfield, indices []uint64) Bitfield {
for _, idx := range indices {
b.SetBitAt(idx, true)
}
return b
}
createBitlist64 := func(n uint64, indices []uint64) *Bitlist64 {
return (selectIndices(NewBitlist64(n), indices)).(*Bitlist64)
}
createBitlist := func(n uint64, indices []uint64) Bitlist {
return (selectIndices(NewBitlist(n), indices)).(Bitlist)
}

for _, tt := range tests {
t.Run(fmt.Sprintf("data:%#x", tt.from), func(t *testing.T) {
got := tt.from.ToBitlist()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ToBitlist(%#x) = %+v, wanted %+v", tt.from, got, tt.want)
source := createBitlist64(tt.size, tt.selectedIndices)
wanted := createBitlist(tt.size, tt.selectedIndices)
t.Run(fmt.Sprintf("size:%d,indices:%v", tt.size, tt.selectedIndices), func(t *testing.T) {
// Convert to Bitlist.
got := source.ToBitlist()
if !reflect.DeepEqual(got, wanted) {
t.Errorf("ToBitlist(%#x) = %#b, wanted %#b", source, got, wanted)
}

// Now convert back, and compare to the original.
gotSource := got.ToBitlist64()
if !reflect.DeepEqual(gotSource, source) {
t.Errorf("ToBitlist(%#x).ToBitlist64() = %+v, wanted %+v", source, gotSource, source)
}
from := tt.from.ToBitlist().ToBitlist64()
if !reflect.DeepEqual(from, tt.from) {
t.Errorf("ToBitlist(%#x).ToBitlist64() = %+v, wanted %+v", tt.from, from, tt.from)

// Make sure that both Bitlist and Bitlist64 Bytes() are equal.
if !bytes.Equal(source.Bytes(), got.Bytes()) {
t.Errorf("original.Bytes() != converted.Bytes() (%#x != %#x)", source.Bytes(), got.Bytes())
}
})
}
Expand Down
Loading

0 comments on commit 7fcea7c

Please sign in to comment.