diff --git a/bitarray/and.go b/bitarray/and.go index 1ef18f2..ca73b03 100644 --- a/bitarray/and.go +++ b/bitarray/and.go @@ -23,6 +23,7 @@ func andSparseWithSparseBitArray(sba, other *sparseBitArray) BitArray { selfIndex := 0 otherIndex := 0 + var resultBlock block // move through the array and compare the blocks if they happen to // intersect @@ -52,9 +53,12 @@ func andSparseWithSparseBitArray(sba, other *sparseBitArray) BitArray { default: // Here, our indices match for both `sba` and `other`. // Time to do the bitwise AND operation and add a block - // to our result list. - indices = append(indices, selfValue) - blocks = append(blocks, sba.blocks[selfIndex].and(other.blocks[otherIndex])) + // to our result list if the block has values in it. + resultBlock = sba.blocks[selfIndex].and(other.blocks[otherIndex]) + if resultBlock > 0 { + indices = append(indices, selfValue) + blocks = append(blocks, resultBlock) + } selfIndex++ otherIndex++ } @@ -92,7 +96,6 @@ func andSparseWithDenseBitArray(sba *sparseBitArray, other *bitArray) BitArray { // We're ready to return break } - ba.blocks[selfIndex] = ba.blocks[selfIndex].and( other.blocks[selfValue]) } diff --git a/bitarray/and_test.go b/bitarray/and_test.go index 83b1166..c8fceb5 100644 --- a/bitarray/and_test.go +++ b/bitarray/and_test.go @@ -43,6 +43,8 @@ func TestAndSparseWithSparseBitArray(t *testing.T) { sba.SetBit(280) other.SetBit(9) other.SetBit(100) + sba.SetBit(1000) + other.SetBit(1001) // bits for which both arrays are set sba.SetBit(1) @@ -54,15 +56,23 @@ func TestAndSparseWithSparseBitArray(t *testing.T) { ba := andSparseWithSparseBitArray(sba, other) + // Bits in both checkBit(t, ba, 1, true) checkBit(t, ba, 30, true) checkBit(t, ba, 2680, true) + // Bits in sba but not other checkBit(t, ba, 3, false) + checkBit(t, ba, 280, false) + checkBit(t, ba, 1000, false) + + // Bits in other but not sba checkBit(t, ba, 9, false) checkBit(t, ba, 100, false) checkBit(t, ba, 2, false) - checkBit(t, ba, 280, false) + + nums := ba.ToNums() + assert.Equal(t, []uint64{1, 30, 2680}, nums) } func TestAndSparseWithDenseBitArray(t *testing.T) { @@ -80,14 +90,20 @@ func TestAndSparseWithDenseBitArray(t *testing.T) { ba := andSparseWithDenseBitArray(sba, other) + // Bits in both checkBit(t, ba, 1, true) checkBit(t, ba, 150, true) + checkBit(t, ba, 300, true) + + // Bits in sba but not other checkBit(t, ba, 155, false) + + // Bits in other but not sba checkBit(t, ba, 156, false) - checkBit(t, ba, 300, true) + } -// Maks sure that the sparse array is trimmed correctly if compared against a +// Make sure that the sparse array is trimmed correctly if compared against a // smaller dense bit array. func TestAndSparseWithSmallerDenseBitArray(t *testing.T) { sba := newSparseBitArray() @@ -106,13 +122,18 @@ func TestAndSparseWithSmallerDenseBitArray(t *testing.T) { ba := andSparseWithDenseBitArray(sba, other) + // Bits in both checkBit(t, ba, 1, true) checkBit(t, ba, 150, true) + + // Bits in sba but not other checkBit(t, ba, 155, false) - checkBit(t, ba, 128, false) checkBit(t, ba, 500, false) checkBit(t, ba, 1200, false) checkBit(t, ba, 1500, false) + + // Bits in other but not sba + checkBit(t, ba, 128, false) } func TestAndDenseWithDenseBitArray(t *testing.T) { @@ -148,6 +169,7 @@ func TestAndSparseWithEmptySparse(t *testing.T) { sba.SetBit(5) ba := andSparseWithSparseBitArray(sba, other) + checkBit(t, ba, 0, false) checkBit(t, ba, 5, false) checkBit(t, ba, 100, false) diff --git a/bitarray/bitarray.go b/bitarray/bitarray.go index 4d479e3..78b6a8d 100644 --- a/bitarray/bitarray.go +++ b/bitarray/bitarray.go @@ -157,6 +157,16 @@ func (ba *bitArray) And(other BitArray) BitArray { return andSparseWithDenseBitArray(other.(*sparseBitArray), ba) } +// Nand will return the result of doing a bitwise and not of the bit array +// with the other bit array on each block. +func (ba *bitArray) Nand(other BitArray) BitArray { + if dba, ok := other.(*bitArray); ok { + return nandDenseWithDenseBitArray(ba, dba) + } + + return nandDenseWithSparseBitArray(ba, other.(*sparseBitArray)) +} + // Reset clears out the bit array. func (ba *bitArray) Reset() { for i := uint64(0); i < uint64(len(ba.blocks)); i++ { @@ -222,6 +232,10 @@ func (ba *bitArray) Blocks() Iterator { return newBitArrayIterator(ba) } +func (ba *bitArray) IsEmpty() bool { + return ba.anyset +} + // complement flips all bits in this array. func (ba *bitArray) complement() { for i := uint64(0); i < uint64(len(ba.blocks)); i++ { diff --git a/bitarray/block.go b/bitarray/block.go index 08a2fcf..d60205a 100644 --- a/bitarray/block.go +++ b/bitarray/block.go @@ -81,6 +81,10 @@ func (b block) and(other block) block { return b & other } +func (b block) nand(other block) block { + return b &^ other +} + func (b block) get(position uint64) bool { return b&block(1< uint64(0); i-- { - err = binary.Read(r, binary.LittleEndian, &nextblock) - if err != nil { - return err + var bytesRead int + intsToRead, bytesRead = Uint64FromBytes(incoming[curLoc : curLoc+intsize]) + if bytesRead < 0 { + return errors.New("Invalid data for BitArray") + } + curLoc += intsize + + var nextblock uint64 + ret.blocks = make([]block, intsToRead) + for i := uint64(0); i < intsToRead; i++ { + nextblock, bytesRead = Uint64FromBytes(incoming[curLoc : curLoc+intsize]) + if bytesRead < 0 { + return errors.New("Invalid data for BitArray") } - ret.blocks = append(ret.blocks, nextblock) + ret.blocks[i] = block(nextblock) + curLoc += intsize } - err = binary.Read(r, binary.LittleEndian, &intsToRead) - if err != nil { - return err + intsToRead, bytesRead = Uint64FromBytes(incoming[curLoc : curLoc+intsize]) + if bytesRead < 0 { + return errors.New("Invalid data for BitArray") } + curLoc += intsize var nextuint uint64 - for i := intsToRead; i > uint64(0); i-- { - err = binary.Read(r, binary.LittleEndian, &nextuint) - if err != nil { - return err + ret.indices = make(uintSlice, intsToRead) + for i := uint64(0); i < intsToRead; i++ { + nextuint, bytesRead = Uint64FromBytes(incoming[curLoc : curLoc+intsize]) + if bytesRead < 0 { + return errors.New("Invalid data for BitArray") } - ret.indices = append(ret.indices, nextuint) + ret.indices[i] = nextuint + curLoc += intsize } - return nil } diff --git a/bitarray/interface.go b/bitarray/interface.go index 7f2e4ca..bb4057a 100644 --- a/bitarray/interface.go +++ b/bitarray/interface.go @@ -61,9 +61,14 @@ type BitArray interface { // And will bitwise and the two bitarrays and return a new bitarray // representing the result. And(other BitArray) BitArray + // Nand will bitwise nand the two bitarrays and return a new bitarray + // representing the result. + Nand(other BitArray) BitArray // ToNums converts this bit array to the list of numbers contained // within it. ToNums() []uint64 + // IsEmpty checks to see if any values are set on the bitarray + IsEmpty() bool } // Iterator defines methods used to iterate over a bit array. diff --git a/bitarray/nand.go b/bitarray/nand.go new file mode 100644 index 0000000..11bee80 --- /dev/null +++ b/bitarray/nand.go @@ -0,0 +1,152 @@ +/* +Copyright 2014 Workiva, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bitarray + +func nandSparseWithSparseBitArray(sba, other *sparseBitArray) BitArray { + // nand is an operation on the incoming array only, so the size will never + // be more than the incoming array, regardless of the size of the other + max := len(sba.indices) + indices := make(uintSlice, 0, max) + blocks := make(blocks, 0, max) + + selfIndex := 0 + otherIndex := 0 + var resultBlock block + + // move through the array and compare the blocks if they happen to + // intersect + for { + if selfIndex == len(sba.indices) { + // The bitarray being operated on is exhausted, so just return + break + } else if otherIndex == len(other.indices) { + // The other array is exhausted. In this case, we assume that we + // are calling nand on empty bit arrays, which is the same as just + // copying the value in the sba array + indices = append(indices, sba.indices[selfIndex]) + blocks = append(blocks, sba.blocks[selfIndex]) + selfIndex++ + continue + } + + selfValue := sba.indices[selfIndex] + otherValue := other.indices[otherIndex] + + switch { + case otherValue < selfValue: + // The `sba` bitarray has a block with a index position + // greater than us. We want to compare with that block + // if possible, so move our `other` index closer to that + // block's index. + otherIndex++ + + case otherValue > selfValue: + // Here, the sba array has blocks that the other array doesn't + // have. In this case, we just copy exactly the sba array values + indices = append(indices, selfValue) + blocks = append(blocks, sba.blocks[selfIndex]) + + // This is the exact logical inverse of the above case. + selfIndex++ + + default: + // Here, our indices match for both `sba` and `other`. + // Time to do the bitwise AND operation and add a block + // to our result list if the block has values in it. + resultBlock = sba.blocks[selfIndex].nand(other.blocks[otherIndex]) + if resultBlock > 0 { + indices = append(indices, selfValue) + blocks = append(blocks, resultBlock) + } + selfIndex++ + otherIndex++ + } + } + + return &sparseBitArray{ + indices: indices, + blocks: blocks, + } +} + +func nandSparseWithDenseBitArray(sba *sparseBitArray, other *bitArray) BitArray { + // Since nand is non-commutative, the resulting array should be sparse, + // and the same length or less than the sparse array + indices := make(uintSlice, 0, len(sba.indices)) + blocks := make(blocks, 0, len(sba.indices)) + + var resultBlock block + + // Loop through the sparse array and match it with the dense array. + for selfIndex, selfValue := range sba.indices { + if selfValue >= uint64(len(other.blocks)) { + // Since the dense array is exhausted, just copy over the data + // from the sparse array + resultBlock = sba.blocks[selfIndex] + indices = append(indices, selfValue) + blocks = append(blocks, resultBlock) + continue + } + + resultBlock = sba.blocks[selfIndex].nand(other.blocks[selfValue]) + if resultBlock > 0 { + indices = append(indices, selfValue) + blocks = append(blocks, resultBlock) + } + } + + return &sparseBitArray{ + indices: indices, + blocks: blocks, + } +} + +func nandDenseWithSparseBitArray(sba *bitArray, other *sparseBitArray) BitArray { + // Since nand is non-commutative, the resulting array should be dense, + // and the same length or less than the dense array + tmp := sba.copy() + ret := tmp.(*bitArray) + + // Loop through the other array and match it with the sba array. + for otherIndex, otherValue := range other.indices { + if otherValue >= uint64(len(ret.blocks)) { + break + } + + ret.blocks[otherValue] = sba.blocks[otherValue].nand(other.blocks[otherIndex]) + } + + ret.setLowest() + ret.setHighest() + + return ret +} + +func nandDenseWithDenseBitArray(dba, other *bitArray) BitArray { + min := uint64(len(dba.blocks)) + + ba := newBitArray(min * s) + + for i := uint64(0); i < min; i++ { + ba.blocks[i] = dba.blocks[i].nand(other.blocks[i]) + } + + ba.setLowest() + ba.setHighest() + + return ba +} diff --git a/bitarray/nand_test.go b/bitarray/nand_test.go new file mode 100644 index 0000000..17f9ecd --- /dev/null +++ b/bitarray/nand_test.go @@ -0,0 +1,316 @@ +/* +Copyright 2014 Workiva, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bitarray + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNandSparseWithSparseBitArray(t *testing.T) { + sba := newSparseBitArray() + other := newSparseBitArray() + + // bits for which only one of the arrays is set + sba.SetBit(3) + sba.SetBit(280) + other.SetBit(9) + other.SetBit(100) + sba.SetBit(1000) + other.SetBit(1001) + + // bits for which both arrays are set + sba.SetBit(1) + other.SetBit(1) + sba.SetBit(2680) + other.SetBit(2680) + sba.SetBit(30) + other.SetBit(30) + + ba := nandSparseWithSparseBitArray(sba, other) + + // Bits in both + checkBit(t, ba, 1, false) + checkBit(t, ba, 30, false) + checkBit(t, ba, 2680, false) + + // Bits in sba but not other + checkBit(t, ba, 3, true) + checkBit(t, ba, 280, true) + checkBit(t, ba, 1000, true) + + // Bits in other but not sba + checkBit(t, ba, 9, false) + checkBit(t, ba, 100, false) + checkBit(t, ba, 2, false) + + nums := ba.ToNums() + assert.Equal(t, []uint64{3, 280, 1000}, nums) +} + +func TestNandSparseWithDenseBitArray(t *testing.T) { + sba := newSparseBitArray() + other := newBitArray(300) + + other.SetBit(1) + sba.SetBit(1) + other.SetBit(150) + sba.SetBit(150) + sba.SetBit(155) + other.SetBit(156) + sba.SetBit(300) + other.SetBit(300) + + ba := nandSparseWithDenseBitArray(sba, other) + + // Bits in both + checkBit(t, ba, 1, false) + checkBit(t, ba, 150, false) + checkBit(t, ba, 300, false) + + // Bits in sba but not other + checkBit(t, ba, 155, true) + + // Bits in other but not sba + checkBit(t, ba, 156, false) + + nums := ba.ToNums() + assert.Equal(t, []uint64{155}, nums) +} + +func TestNandDenseWithSparseBitArray(t *testing.T) { + sba := newBitArray(300) + other := newSparseBitArray() + + other.SetBit(1) + sba.SetBit(1) + other.SetBit(150) + sba.SetBit(150) + sba.SetBit(155) + other.SetBit(156) + sba.SetBit(300) + other.SetBit(300) + + ba := nandDenseWithSparseBitArray(sba, other) + + // Bits in both + checkBit(t, ba, 1, false) + checkBit(t, ba, 150, false) + checkBit(t, ba, 300, false) + + // Bits in sba but not other + checkBit(t, ba, 155, true) + + // Bits in other but not sba + checkBit(t, ba, 156, false) + + nums := ba.ToNums() + assert.Equal(t, []uint64{155}, nums) +} + +func TestNandSparseWithSmallerDenseBitArray(t *testing.T) { + sba := newSparseBitArray() + other := newBitArray(512) + + other.SetBit(1) + sba.SetBit(1) + other.SetBit(150) + sba.SetBit(150) + sba.SetBit(155) + sba.SetBit(500) + + other.SetBit(128) + sba.SetBit(1500) + sba.SetBit(1200) + + ba := nandSparseWithDenseBitArray(sba, other) + + // Bits in both + checkBit(t, ba, 1, false) + checkBit(t, ba, 150, false) + + // Bits in sba but not other + checkBit(t, ba, 155, true) + checkBit(t, ba, 500, true) + checkBit(t, ba, 1200, true) + checkBit(t, ba, 1500, true) + + // Bits in other but not sba + checkBit(t, ba, 128, false) + + nums := ba.ToNums() + assert.Equal(t, []uint64{155, 500, 1200, 1500}, nums) +} + +func TestNandDenseWithDenseBitArray(t *testing.T) { + dba := newBitArray(1000) + other := newBitArray(2000) + + dba.SetBit(1) + other.SetBit(18) + dba.SetBit(222) + other.SetBit(222) + other.SetBit(1501) + + ba := nandDenseWithDenseBitArray(dba, other) + + // Bits in both + checkBit(t, ba, 222, false) + + // Bits in dba and not other + checkBit(t, ba, 1, true) + + // Bits in other + checkBit(t, ba, 18, false) + + // Bits in neither + checkBit(t, ba, 0, false) + checkBit(t, ba, 3, false) + + // check that the ba is the minimum of the size of `dba` and `other` + // (dense bitarrays return an error on an out-of-bounds access) + _, err := ba.GetBit(1500) + assert.Equal(t, OutOfRangeError(1500), err) + _, err = ba.GetBit(1501) + assert.Equal(t, OutOfRangeError(1501), err) + + nums := ba.ToNums() + assert.Equal(t, []uint64{1}, nums) +} + +func TestNandSparseWithEmptySparse(t *testing.T) { + sba := newSparseBitArray() + other := newSparseBitArray() + + sba.SetBit(5) + + ba := nandSparseWithSparseBitArray(sba, other) + + checkBit(t, ba, 0, false) + checkBit(t, ba, 5, true) + checkBit(t, ba, 100, false) +} + +func TestNandSparseWithEmptyDense(t *testing.T) { + sba := newSparseBitArray() + other := newBitArray(1000) + + sba.SetBit(5) + ba := nandSparseWithDenseBitArray(sba, other) + checkBit(t, ba, 5, true) + + sba.Reset() + other.SetBit(5) + + ba = nandSparseWithDenseBitArray(sba, other) + checkBit(t, ba, 5, false) +} + +func TestNandDenseWithEmptyDense(t *testing.T) { + dba := newBitArray(1000) + other := newBitArray(1000) + + dba.SetBit(5) + ba := nandDenseWithDenseBitArray(dba, other) + checkBit(t, ba, 5, true) + + dba.Reset() + other.SetBit(5) + ba = nandDenseWithDenseBitArray(dba, other) + checkBit(t, ba, 5, false) +} + +func BenchmarkNandSparseWithSparse(b *testing.B) { + numItems := uint64(160000) + sba := newSparseBitArray() + other := newSparseBitArray() + + for i := uint64(0); i < numItems; i += s { + if i%200 == 0 { + sba.SetBit(i) + } else if i%300 == 0 { + other.SetBit(i) + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + nandSparseWithSparseBitArray(sba, other) + } +} + +func BenchmarkNandSparseWithDense(b *testing.B) { + numItems := uint64(160000) + sba := newSparseBitArray() + other := newBitArray(numItems) + + for i := uint64(0); i < numItems; i += s { + if i%2 == 0 { + sba.SetBit(i) + } else if i%3 == 0 { + other.SetBit(i) + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + nandSparseWithDenseBitArray(sba, other) + } +} + +func BenchmarkNandDenseWithSparse(b *testing.B) { + numItems := uint64(160000) + ba := newBitArray(numItems) + other := newSparseBitArray() + + for i := uint64(0); i < numItems; i += s { + if i%2 == 0 { + ba.SetBit(i) + } else if i%3 == 0 { + other.SetBit(i) + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + nandDenseWithSparseBitArray(ba, other) + } +} + +func BenchmarkNandDenseWithDense(b *testing.B) { + numItems := uint64(160000) + dba := newBitArray(numItems) + other := newBitArray(numItems) + + for i := uint64(0); i < numItems; i += s { + if i%2 == 0 { + dba.SetBit(i) + } else if i%3 == 0 { + other.SetBit(i) + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + nandDenseWithDenseBitArray(dba, other) + } +} diff --git a/bitarray/sparse_bitarray.go b/bitarray/sparse_bitarray.go index e4dee1c..8cb7b6c 100644 --- a/bitarray/sparse_bitarray.go +++ b/bitarray/sparse_bitarray.go @@ -245,6 +245,22 @@ func (sba *sparseBitArray) And(other BitArray) BitArray { return andSparseWithDenseBitArray(sba, other.(*bitArray)) } +// Nand will return the result of doing a bitwise and not of the bit array +// with the other bit array on each block. +func (sba *sparseBitArray) Nand(other BitArray) BitArray { + if ba, ok := other.(*sparseBitArray); ok { + return nandSparseWithSparseBitArray(sba, ba) + } + + return nandSparseWithDenseBitArray(sba, other.(*bitArray)) +} + +func (sba *sparseBitArray) IsEmpty() bool { + // This works because the and, nand and delete functions only + // keep values that have a non-zero block. + return len(sba.indices) == 0 +} + func (sba *sparseBitArray) copy() *sparseBitArray { blocks := make(blocks, len(sba.blocks)) copy(blocks, sba.blocks)