Skip to content

Commit ef1ae27

Browse files
committed
Add functions to allow storage of bitarrays
Add functions that allow bitarrays to be stored in offline files or databases.
1 parent 064f3ea commit ef1ae27

File tree

4 files changed

+189
-1
lines changed

4 files changed

+189
-1
lines changed

bitarray/bitarray.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ efficient way. This is *NOT* a threadsafe package.
2020
*/
2121
package bitarray
2222

23+
import (
24+
"encoding/binary"
25+
"io"
26+
)
27+
2328
// bitArray is a struct that maintains state of a bit array.
2429
type bitArray struct {
2530
blocks []block
@@ -265,6 +270,70 @@ func (ba *bitArray) copy() BitArray {
265270
}
266271
}
267272

273+
// Write serializes the bitArray and its data and sends it to the writer.
274+
func Write(w io.Writer, ba *bitArray) error {
275+
err := binary.Write(w, binary.LittleEndian, ba.lowest)
276+
if err != nil {
277+
return err
278+
}
279+
err = binary.Write(w, binary.LittleEndian, ba.highest)
280+
if err != nil {
281+
return err
282+
}
283+
284+
var encodedanyset uint8
285+
if ba.anyset {
286+
encodedanyset = 1
287+
} else {
288+
encodedanyset = 0
289+
}
290+
err = binary.Write(w, binary.LittleEndian, encodedanyset)
291+
if err != nil {
292+
return err
293+
}
294+
295+
err = binary.Write(w, binary.LittleEndian, ba.blocks)
296+
return err
297+
}
298+
299+
// Read takes a reader of a serialized bitArray created by the Write function,
300+
// and returns a bitArray object.
301+
func Read(r io.Reader) (*bitArray, error) {
302+
ret := &bitArray{}
303+
304+
err := binary.Read(r, binary.LittleEndian, &ret.lowest)
305+
if err != nil {
306+
return nil, err
307+
}
308+
309+
err = binary.Read(r, binary.LittleEndian, &ret.highest)
310+
if err != nil {
311+
return nil, err
312+
}
313+
314+
var encodedanyset uint8
315+
err = binary.Read(r, binary.LittleEndian, &encodedanyset)
316+
if err != nil {
317+
return nil, err
318+
}
319+
320+
// anyset defaults to false so we don't need an else statement
321+
if encodedanyset == 1 {
322+
ret.anyset = true
323+
}
324+
325+
var nextblock block
326+
err = binary.Read(r, binary.LittleEndian, &nextblock)
327+
for err == nil {
328+
ret.blocks = append(ret.blocks, nextblock)
329+
err = binary.Read(r, binary.LittleEndian, &nextblock)
330+
}
331+
if err != io.EOF {
332+
return nil, err
333+
}
334+
return ret, nil
335+
}
336+
268337
// newBitArray returns a new dense BitArray at the specified size. This is a
269338
// separate private constructor so unit tests don't have to constantly cast the
270339
// BitArray interface to the concrete type.

bitarray/bitarray_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package bitarray
1818

1919
import (
20+
"bytes"
2021
"testing"
2122

2223
"github.com/stretchr/testify/assert"
@@ -472,3 +473,30 @@ func BenchmarkBitArrayToNums(b *testing.B) {
472473
ba.ToNums()
473474
}
474475
}
476+
477+
func TestBitArrayReadWrite(t *testing.T) {
478+
numItems := uint64(1280)
479+
input := newBitArray(numItems)
480+
481+
for i := uint64(0); i < numItems; i++ {
482+
if i%3 == 0 {
483+
input.SetBit(i)
484+
}
485+
}
486+
487+
writebuf := new(bytes.Buffer)
488+
err := Write(writebuf, input)
489+
assert.Equal(t, err, nil)
490+
491+
// 1280 bits = 20 blocks = 160 bytes, plus lowest and highest at
492+
// 128 bits = 16 bytes plus 1 byte for the anyset param
493+
assert.Equal(t, len(writebuf.Bytes()), 177)
494+
495+
expected := []byte{0, 0, 0, 0, 0, 0, 0, 0, 254}
496+
assert.Equal(t, expected, writebuf.Bytes()[:9])
497+
498+
readbuf := bytes.NewReader(writebuf.Bytes())
499+
output, err := Read(readbuf)
500+
assert.Equal(t, err, nil)
501+
assert.True(t, input.Equals(output))
502+
}

bitarray/sparse_bitarray.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ limitations under the License.
1616

1717
package bitarray
1818

19-
import "sort"
19+
import (
20+
"encoding/binary"
21+
"io"
22+
"sort"
23+
)
2024

2125
// uintSlice is an alias for a slice of ints. Len, Swap, and Less
2226
// are exported to fulfill an interface needed for the search
@@ -296,6 +300,67 @@ func (sba *sparseBitArray) IntersectsBetween(other BitArray, start, stop uint64)
296300
return true
297301
}
298302

303+
// WriteSparse serializes the sparseBitArray and passes it to the writer.
304+
func WriteSparse(w io.Writer, ba *sparseBitArray) error {
305+
blocksLen := uint64(len(ba.blocks))
306+
indexLen := uint64(len(ba.indices))
307+
308+
err := binary.Write(w, binary.LittleEndian, blocksLen)
309+
if err != nil {
310+
return err
311+
}
312+
313+
err = binary.Write(w, binary.LittleEndian, ba.blocks)
314+
if err != nil {
315+
return err
316+
}
317+
318+
err = binary.Write(w, binary.LittleEndian, indexLen)
319+
if err != nil {
320+
return err
321+
}
322+
323+
err = binary.Write(w, binary.LittleEndian, ba.indices)
324+
return err
325+
}
326+
327+
// ReadSparse takes a reader of a serialized sparseBitArray created by the
328+
// WriteSparse function, and returns a sparseBitArray object.
329+
func ReadSparse(r io.Reader) (*sparseBitArray, error) {
330+
ret := &sparseBitArray{}
331+
332+
var intsToRead uint64
333+
err := binary.Read(r, binary.LittleEndian, &intsToRead)
334+
if err != nil {
335+
return nil, err
336+
}
337+
338+
var nextblock block
339+
for i := intsToRead; i > uint64(0); i-- {
340+
err = binary.Read(r, binary.LittleEndian, &nextblock)
341+
if err != nil {
342+
return nil, err
343+
}
344+
ret.blocks = append(ret.blocks, nextblock)
345+
}
346+
347+
err = binary.Read(r, binary.LittleEndian, &intsToRead)
348+
if err != nil {
349+
return nil, err
350+
}
351+
352+
var nextuint uint64
353+
for i := intsToRead; i > uint64(0); i-- {
354+
err = binary.Read(r, binary.LittleEndian, &nextuint)
355+
if err != nil {
356+
return nil, err
357+
}
358+
ret.indices = append(ret.indices, nextuint)
359+
}
360+
361+
return ret, nil
362+
}
363+
299364
func newSparseBitArray() *sparseBitArray {
300365
return &sparseBitArray{}
301366
}

bitarray/sparse_bitarray_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package bitarray
1818

1919
import (
20+
"bytes"
2021
"testing"
2122

2223
"github.com/stretchr/testify/assert"
@@ -308,3 +309,28 @@ func BenchmarkSparseBitArrayToNums(b *testing.B) {
308309
sba.ToNums()
309310
}
310311
}
312+
313+
func TestSparseBitArrayReadWrite(t *testing.T) {
314+
numItems := uint64(1280)
315+
input := newSparseBitArray()
316+
317+
for i := uint64(0); i < numItems; i++ {
318+
if i%3 == 0 {
319+
input.SetBit(i)
320+
}
321+
}
322+
323+
writebuf := new(bytes.Buffer)
324+
err := WriteSparse(writebuf, input)
325+
assert.Equal(t, err, nil)
326+
327+
assert.Equal(t, len(writebuf.Bytes()), 336)
328+
329+
expected := []byte{20, 0, 0, 0, 0, 0, 0, 0, 73}
330+
assert.Equal(t, expected, writebuf.Bytes()[:9])
331+
332+
readbuf := bytes.NewReader(writebuf.Bytes())
333+
output, err := ReadSparse(readbuf)
334+
assert.Equal(t, err, nil)
335+
assert.True(t, input.Equals(output))
336+
}

0 commit comments

Comments
 (0)