mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-10-09 04:17:51 +00:00
added go-fuzz testing for roaring ops vs naive implementation
This commit is contained in:
parent
8760ed77b2
commit
4f8b3f650e
3 changed files with 923 additions and 0 deletions
|
|
@ -16,6 +16,15 @@
|
|||
|
||||
package roaring
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// FuzzBitmapUnmarshalBinary fuzz tests the unmarshaling of binary
|
||||
// to both Pilosa and official roaring formats.
|
||||
func FuzzBitmapUnmarshalBinary(data []byte) int {
|
||||
b := NewBitmap()
|
||||
err := b.UnmarshalBinary(data)
|
||||
|
|
@ -24,3 +33,291 @@ func FuzzBitmapUnmarshalBinary(data []byte) int {
|
|||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// FuzzRoaringOps fuzz tests different operations on roaring bitmaps,
|
||||
// comparing the results to a naive implementation of the operations
|
||||
// on uint64 slices.
|
||||
func FuzzRoaringOps(data []byte) int {
|
||||
// number of uint64s not to include
|
||||
const reserved = 4
|
||||
// flipping is too inefficient for large values of end - start
|
||||
// and will cause go-fuzz to hang if not controlled.
|
||||
const maxFlips = 1000000
|
||||
|
||||
arr := bytesToUint64s(data)
|
||||
if len(arr) <= reserved {
|
||||
return 0
|
||||
}
|
||||
|
||||
// start > end is possible. This will test correctness in unexpected conditions.
|
||||
start, end, split, rand := arr[0], arr[1], int(arr[2]), arr[3]
|
||||
|
||||
// don't include start, end, split, rand in the slices
|
||||
if split < reserved {
|
||||
split = reserved
|
||||
}
|
||||
// ensure slice in bounds
|
||||
if split > len(arr) {
|
||||
split = len(arr)
|
||||
}
|
||||
// using removeSliceDuplicates guarantees that the slice inputs of the
|
||||
// following functions do not have duplicates and are sorted, just as
|
||||
// the Roaring Bitmap implementations are.
|
||||
s1 := removeSliceDuplicates(arr[reserved:split])
|
||||
s2 := removeSliceDuplicates(arr[split:])
|
||||
if len(s1) == 0 {
|
||||
s1 = nil
|
||||
}
|
||||
if len(s2) == 0 {
|
||||
s2 = nil
|
||||
}
|
||||
bm1 := NewBitmap(arr[reserved:split]...)
|
||||
bm2 := NewBitmap(arr[split:]...)
|
||||
|
||||
expected := s1
|
||||
actual := bm1.Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("first slice:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
|
||||
expected = s2
|
||||
actual = bm2.Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("second slice:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
// Pure functions
|
||||
|
||||
expected = []uint64{maxInSlice(s1), maxInSlice(s2)}
|
||||
actual = []uint64{bm1.Max(), bm2.Max()}
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("max values:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
|
||||
expected = intersectSlice(s1, s2)
|
||||
actual = bm1.Intersect(bm2).Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("intersection:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
|
||||
expected = unionSlice(s1, s2)
|
||||
actual = bm1.Union(bm2).Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("union:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
|
||||
expected = differenceSlice(s1, s2)
|
||||
actual = bm1.Difference(bm2).Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("difference:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
|
||||
expected = xorSlice(s1, s2)
|
||||
actual = bm1.Xor(bm2).Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("XOR:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
|
||||
if (len(s1) > 0) != bm1.Any() {
|
||||
panic(fmt.Sprintf("any:\n %v has %v values but got %v which has %v values", s1, len(s1), bm1.Slice(), bm1.Any()))
|
||||
}
|
||||
if (len(s2) > 0) != bm2.Any() {
|
||||
panic(fmt.Sprintf("any:\n %v has %v values but got %v which has %v values", s2, len(s2), bm2.Slice(), bm2.Any()))
|
||||
}
|
||||
|
||||
expected = []uint64{uint64(len(s1)), uint64(len(s2))}
|
||||
actual = []uint64{bm1.Count(), bm2.Count()}
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("count:\n expected: %v\n got: %v", expected, actual))
|
||||
}
|
||||
|
||||
expect := countRangeSlice(s1, start, end)
|
||||
got := bm1.CountRange(start, end)
|
||||
if expect != got {
|
||||
panic(fmt.Sprintf("count range:\n count from %v to %v in slice %v and bitmap %v:\n expected %v got %v",
|
||||
start, end, s1, bm1.Slice(), expect, got))
|
||||
}
|
||||
expect = countRangeSlice(s2, start, end)
|
||||
got = bm2.CountRange(start, end)
|
||||
if expect != got {
|
||||
panic(fmt.Sprintf("count range:\n count from %v to %v in slice %v and bitmap %v:\n expected %v got %v",
|
||||
start, end, s2, bm2.Slice(), expect, got))
|
||||
}
|
||||
|
||||
expected = rangeSlice(s1, start, end)
|
||||
actual = bm1.SliceRange(start, end)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("slice range:\n from %v to %v in slice %v and bitmap %v:\n expected %v\n got %v",
|
||||
start, end, s1, bm1.Slice(), expected, actual))
|
||||
}
|
||||
expected = rangeSlice(s2, start, end)
|
||||
actual = bm2.SliceRange(start, end)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("slice range:\n from %v to %v in slice %v and bitmap %v:\n expected %v\n got %v",
|
||||
start, end, s2, bm2.Slice(), expected, actual))
|
||||
}
|
||||
|
||||
expect = uint64(len(intersectSlice(s1, s2)))
|
||||
got = bm1.IntersectionCount(bm2)
|
||||
if expect != got {
|
||||
panic(fmt.Sprintf("intersection count:\n expected %v got %v", expect, got))
|
||||
}
|
||||
|
||||
_, found := containedInSlice(s1, rand)
|
||||
if found != bm1.Contains(rand) {
|
||||
panic(fmt.Sprintf("contains:\n %v contains %v: %v\n %v contains %v: %v", s1, rand, found,
|
||||
bm1.Slice(), rand, bm1.Contains(rand)))
|
||||
}
|
||||
_, found = containedInSlice(s2, rand)
|
||||
if found != bm2.Contains(rand) {
|
||||
panic(fmt.Sprintf("contains:\n %v contains %v: %v\n %v contains %v: %v", s2, rand, found,
|
||||
bm2.Slice(), rand, bm2.Contains(rand)))
|
||||
}
|
||||
|
||||
if end-start < maxFlips {
|
||||
expected = flipSlice(s1, start, end)
|
||||
actual = bm1.Flip(start, end).Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("flip:\n from %v to %v in slice %v and bitmap %v\n expected %v\n got %v",
|
||||
start, end, s1, bm1.Slice(), expected, actual))
|
||||
}
|
||||
expected = flipSlice(s2, start, end)
|
||||
actual = bm2.Flip(start, end).Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("flip:\n from %v to %v in slice %v and bitmap %v\n expected %v\n got %v",
|
||||
start, end, s2, bm2.Slice(), expected, actual))
|
||||
}
|
||||
}
|
||||
|
||||
expected = make([]uint64, 0)
|
||||
actual = make([]uint64, 0)
|
||||
forEachInSlice(s1, func(v uint64) { expected = append(expected, v) })
|
||||
bm1.ForEach(func(v uint64) { actual = append(actual, v) })
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("for each:\n expected %v\n got %v", expected, actual))
|
||||
}
|
||||
expected = make([]uint64, 0)
|
||||
actual = make([]uint64, 0)
|
||||
forEachInSlice(s2, func(v uint64) { expected = append(expected, v) })
|
||||
bm2.ForEach(func(v uint64) { actual = append(actual, v) })
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("for each:\n expected %v\n got %v", expected, actual))
|
||||
}
|
||||
|
||||
expected = make([]uint64, 0)
|
||||
actual = make([]uint64, 0)
|
||||
forEachInRangeSlice(s1, start, end, func(v uint64) { expected = append(expected, v) })
|
||||
bm1.ForEachRange(start, end, func(v uint64) { actual = append(actual, v) })
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("for each in range:\n expected %v\n got %v", expected, actual))
|
||||
}
|
||||
expected = make([]uint64, 0)
|
||||
actual = make([]uint64, 0)
|
||||
forEachInRangeSlice(s2, start, end, func(v uint64) { expected = append(expected, v) })
|
||||
bm2.ForEachRange(start, end, func(v uint64) { actual = append(actual, v) })
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("for each in range:\n expected %v\n got %v", expected, actual))
|
||||
}
|
||||
|
||||
// Impure functions
|
||||
// The following tests operations that mutate bitmaps.
|
||||
|
||||
nbm1, nbm2 := bm1.Clone(), bm2.Clone()
|
||||
expected = shiftSlice(s1, 1)
|
||||
tempBM, _ := nbm1.Shift(1)
|
||||
actual = tempBM.Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("shift:\n in slice %v and bitmap %v \n expected %v\n got %v",
|
||||
s1, bm1.Slice(), expected, actual))
|
||||
}
|
||||
expected = shiftSlice(s2, 1)
|
||||
tempBM, _ = nbm2.Shift(1)
|
||||
actual = tempBM.Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("shift:\n in slice %v and bitmap %v \n expected %v\n got %v",
|
||||
s2, bm2.Slice(), expected, actual))
|
||||
}
|
||||
|
||||
// reuse start and end as random values
|
||||
rand2 := start
|
||||
rand3 := end
|
||||
|
||||
nbm1 = bm1.Clone()
|
||||
expected, echanged := addNToSlice(s1, rand)
|
||||
achanged := nbm1.DirectAddN(rand)
|
||||
actual = nbm1.Slice()
|
||||
if echanged != achanged || !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("directAddN:\n adding %v in slice %v and bitmap %v \n expected %v and %v changed \n got %v and %v changed",
|
||||
rand, s1, bm1.Slice(), expected, echanged, actual, achanged))
|
||||
}
|
||||
nbm2 = bm2.Clone()
|
||||
expected, echanged = addNToSlice(s2, rand2, rand3)
|
||||
achanged = nbm2.DirectAddN(rand2, rand3)
|
||||
actual = nbm2.Slice()
|
||||
if echanged != achanged || !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("directAddN:\n adding %v and %v in slice %v and bitmap %v \n expected %v and %v changed \n got %v and %v changed",
|
||||
rand2, rand3, s2, bm2.Slice(), expected, echanged, actual, achanged))
|
||||
}
|
||||
|
||||
nbm1 = bm1.Clone()
|
||||
expected, echanged = removeNFromSlice(s1, rand2, rand3)
|
||||
achanged = nbm1.DirectRemoveN(rand2, rand3)
|
||||
actual = nbm1.Slice()
|
||||
if echanged != achanged || !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("directRemoveN\n removing %v and %v in slice %v and bitmap %v \n expected %v and %v changed \n got %v and %v changed",
|
||||
rand2, rand3, s1, bm1.Slice(), expected, echanged, actual, achanged))
|
||||
}
|
||||
nbm2 = bm2.Clone()
|
||||
expected, echanged = removeNFromSlice(s2, rand)
|
||||
achanged = nbm2.DirectRemoveN(rand)
|
||||
actual = nbm2.Slice()
|
||||
if echanged != achanged || !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("directRemoveN:\n removing %v in slice %v and bitmap %v \n expected %v and %v changed \n got %v and %v changed",
|
||||
rand, s2, bm2.Slice(), expected, echanged, actual, achanged))
|
||||
}
|
||||
|
||||
nbm1, nbm2 = bm1.Clone(), bm2.Clone()
|
||||
expected = unionSlice(s1, s2)
|
||||
nbm1.UnionInPlace(nbm2)
|
||||
actual = nbm1.Slice()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
panic(fmt.Sprintf("union in place:\n expected %v\n got %v", expected, actual))
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func bytesToUint64s(data []byte) []uint64 {
|
||||
const uint64Size = 8
|
||||
size := len(data) / uint64Size
|
||||
|
||||
slice := make([]uint64, 0)
|
||||
for i := 0; i < size; i++ {
|
||||
offset := i * uint64Size
|
||||
num := binary.LittleEndian.Uint64(data[offset : offset+uint64Size])
|
||||
slice = append(slice, num)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// copy and paste the following to a main file to run.
|
||||
// path should be the absolute path to the corpus.
|
||||
// make sure filename is not already in the corpus.
|
||||
func addSliceToCorpus(slice []uint64, filename, path string) {
|
||||
data := uint64sToBytes(slice)
|
||||
err := ioutil.WriteFile(path+"/"+filename, data, 0777)
|
||||
if err != nil {
|
||||
fmt.Printf("could not write to file: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func uint64sToBytes(slice []uint64) []byte {
|
||||
const uint64Size = 8
|
||||
size := len(slice) * uint64Size
|
||||
|
||||
data := make([]byte, size)
|
||||
for i := 0; i < len(slice); i++ {
|
||||
offset := i * uint64Size
|
||||
binary.LittleEndian.PutUint64(data[offset:offset+uint64Size], slice[i])
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
|
|
|||
321
roaring/naive.go
Normal file
321
roaring/naive.go
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// 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 roaring
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// The following functions reimplement Roaring Bitmap methods, but done naively on
|
||||
// uint64 slices. Most of these functions are inefficient, which is acceptable because
|
||||
// this is purely for testing consistency with Roaring internal operations. Thus, the
|
||||
// functions should be easily guaranteed to produce the correct results.
|
||||
|
||||
func sortSlice(slice []uint64) {
|
||||
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
|
||||
}
|
||||
|
||||
// removeSliceDuplicates removes duplicate values
|
||||
// in the slice and sorts the output.
|
||||
func removeSliceDuplicates(slice []uint64) []uint64 {
|
||||
// just throw slice into a map and
|
||||
// get the values out again
|
||||
hash := make(map[uint64]bool)
|
||||
for _, val := range slice {
|
||||
hash[val] = true
|
||||
}
|
||||
unique := make([]uint64, 0)
|
||||
for key := range hash {
|
||||
unique = append(unique, key)
|
||||
}
|
||||
|
||||
if len(unique) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortSlice(unique)
|
||||
return unique
|
||||
}
|
||||
|
||||
// intersect intersects two []uint64s, removing any duplicates
|
||||
// and sorting the final output.
|
||||
func intersectSlice(s1, s2 []uint64) []uint64 {
|
||||
// throw both slices in maps
|
||||
hash1 := make(map[uint64]bool)
|
||||
for _, val := range s1 {
|
||||
hash1[val] = true
|
||||
}
|
||||
hash2 := make(map[uint64]bool)
|
||||
for _, val := range s2 {
|
||||
hash2[val] = true
|
||||
}
|
||||
|
||||
intersection := make([]uint64, 0)
|
||||
|
||||
// look for keys from hash1 also in hash2
|
||||
for key := range hash1 {
|
||||
if _, found := hash2[key]; found {
|
||||
intersection = append(intersection, key)
|
||||
}
|
||||
}
|
||||
|
||||
if len(intersection) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortSlice(intersection)
|
||||
return intersection
|
||||
}
|
||||
|
||||
// union unions two []uint64s and sorts the output.
|
||||
func unionSlice(s1, s2 []uint64) []uint64 {
|
||||
// just dump both slices in a map
|
||||
// and get the values out again
|
||||
hash := make(map[uint64]bool)
|
||||
for _, val := range s1 {
|
||||
hash[val] = true
|
||||
}
|
||||
for _, val := range s2 {
|
||||
hash[val] = true
|
||||
}
|
||||
union := make([]uint64, 0)
|
||||
|
||||
for key := range hash {
|
||||
union = append(union, key)
|
||||
}
|
||||
|
||||
if len(union) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortSlice(union)
|
||||
return union
|
||||
}
|
||||
|
||||
// maxSlice returns the max in the slice.
|
||||
func maxInSlice(slice []uint64) uint64 {
|
||||
if len(slice) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
max := uint64(0)
|
||||
for _, val := range slice {
|
||||
if val > max {
|
||||
max = val
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// differenceSlice returns a slice containing the values
|
||||
// present in the first slice but not in the second.
|
||||
func differenceSlice(s1, s2 []uint64) []uint64 {
|
||||
// throw s2 in a map, check if each value
|
||||
// in s1 is also in that map
|
||||
hash := make(map[uint64]bool)
|
||||
for _, val := range s2 {
|
||||
hash[val] = true
|
||||
}
|
||||
diff := make([]uint64, 0)
|
||||
for _, val := range s1 {
|
||||
if _, found := hash[val]; !found {
|
||||
diff = append(diff, val)
|
||||
}
|
||||
}
|
||||
// make sure duplicates in s1 are not added
|
||||
diff = removeSliceDuplicates(diff)
|
||||
return diff
|
||||
}
|
||||
|
||||
// xorSlice returns an array containing the values
|
||||
// present in exactly one of the two slices.
|
||||
func xorSlice(s1, s2 []uint64) []uint64 {
|
||||
// throw both slices in maps
|
||||
hash1 := make(map[uint64]bool)
|
||||
for _, val := range s1 {
|
||||
hash1[val] = true
|
||||
}
|
||||
hash2 := make(map[uint64]bool)
|
||||
for _, val := range s2 {
|
||||
hash2[val] = true
|
||||
}
|
||||
|
||||
xor := make([]uint64, 0)
|
||||
// add all values in hash1 not in hash2
|
||||
for key := range hash1 {
|
||||
if _, found := hash2[key]; !found {
|
||||
xor = append(xor, key)
|
||||
}
|
||||
}
|
||||
// add all values in hash2 not in hash1
|
||||
for key := range hash2 {
|
||||
if _, found := hash1[key]; !found {
|
||||
xor = append(xor, key)
|
||||
}
|
||||
}
|
||||
|
||||
if len(xor) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortSlice(xor)
|
||||
return xor
|
||||
}
|
||||
|
||||
// shiftSlice adds n to each element and sorts the slice, but ignores any values that
|
||||
// will cause an overflow. This does not modify the original slice, unlike the Roaring implementation.
|
||||
func shiftSlice(slice []uint64, n int) []uint64 {
|
||||
shifted := make([]uint64, 0)
|
||||
for _, val := range slice {
|
||||
if uint64(n) <= math.MaxUint64-val {
|
||||
shifted = append(shifted, val+uint64(n))
|
||||
}
|
||||
}
|
||||
|
||||
if len(shifted) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortSlice(shifted)
|
||||
return shifted
|
||||
}
|
||||
|
||||
// forEachSlice executes fn for each element in the slice.
|
||||
func forEachInSlice(slice []uint64, fn func(uint64)) {
|
||||
for _, val := range slice {
|
||||
fn(val)
|
||||
}
|
||||
}
|
||||
|
||||
// forEachRangeSlice executes fn for each element in slice that is in [start, end).
|
||||
func forEachInRangeSlice(slice []uint64, start, end uint64, fn func(uint64)) {
|
||||
for _, val := range slice {
|
||||
if start <= val && val < end {
|
||||
fn(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// containedInSlice returns the index of the first instance of v and true
|
||||
// if v is in slice and returns -1 and false otherwise.
|
||||
func containedInSlice(slice []uint64, v uint64) (int, bool) {
|
||||
for idx := range slice {
|
||||
if v == slice[idx] {
|
||||
return idx, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// addNToSlice adds the contents of a to slice and returns the new slice and
|
||||
// number of values successfully added. This somewhat mimics *Bitmap.DirectAddN
|
||||
// and but does not modify slice in place, so it returns that new slice instead.
|
||||
func addNToSlice(slice []uint64, a ...uint64) ([]uint64, int) {
|
||||
newSlice := make([]uint64, len(slice))
|
||||
copy(newSlice, slice)
|
||||
changed := 0
|
||||
|
||||
for _, val := range a {
|
||||
if _, found := containedInSlice(newSlice, val); !found {
|
||||
newSlice = append(newSlice, val)
|
||||
changed++
|
||||
}
|
||||
}
|
||||
|
||||
if len(newSlice) == 0 {
|
||||
return nil, changed
|
||||
}
|
||||
sortSlice(newSlice)
|
||||
return newSlice, changed
|
||||
}
|
||||
|
||||
// removeNFromSlice removes the contents of a from slice and returns the new slice and
|
||||
// number of values successfully removed. This somewhat mimics *Bitmap.DirectRemoveN
|
||||
// and but does not modify slice in place, so it returns that new slice instead.
|
||||
func removeNFromSlice(slice []uint64, a ...uint64) ([]uint64, int) {
|
||||
newSlice := make([]uint64, len(slice))
|
||||
copy(newSlice, slice)
|
||||
changed := 0
|
||||
|
||||
for _, val := range a {
|
||||
if i, found := containedInSlice(newSlice, val); found {
|
||||
newSlice = append(newSlice[:i], newSlice[i+1:]...)
|
||||
changed++
|
||||
}
|
||||
}
|
||||
|
||||
if len(newSlice) == 0 {
|
||||
return nil, changed
|
||||
}
|
||||
sortSlice(newSlice)
|
||||
return newSlice, changed
|
||||
}
|
||||
|
||||
// countRangeSlice returns the number of values in slice that are in [start, end).
|
||||
func countRangeSlice(slice []uint64, start, end uint64) uint64 {
|
||||
count := uint64(0)
|
||||
for _, val := range slice {
|
||||
if start <= val && val < end {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// rangeSlice returns a sorted slice of integers between [start, end).
|
||||
func rangeSlice(slice []uint64, start, end uint64) []uint64 {
|
||||
newSlice := make([]uint64, 0)
|
||||
for _, val := range slice {
|
||||
if start <= val && val < end {
|
||||
newSlice = append(newSlice, val)
|
||||
}
|
||||
}
|
||||
|
||||
if len(newSlice) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortSlice(newSlice)
|
||||
return newSlice
|
||||
}
|
||||
|
||||
// flipSplice returns a slice containing all numbers in [start, end]
|
||||
// that are not in the original slice, as well as the numbers in the
|
||||
// original slice not in [start, end].
|
||||
func flipSlice(slice []uint64, start, end uint64) []uint64 {
|
||||
if start > end {
|
||||
sortSlice(slice)
|
||||
return slice
|
||||
}
|
||||
|
||||
flipped := make([]uint64, 0)
|
||||
// add values in slice outside [start, end]
|
||||
hash := make(map[uint64]bool)
|
||||
for _, val := range slice {
|
||||
hash[val] = true
|
||||
}
|
||||
for val := range hash {
|
||||
if val < start || val > end {
|
||||
flipped = append(flipped, val)
|
||||
}
|
||||
}
|
||||
|
||||
for i := start; i <= end; i++ {
|
||||
if _, found := containedInSlice(slice, i); !found {
|
||||
flipped = append(flipped, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(flipped) == 0 {
|
||||
return nil
|
||||
}
|
||||
sortSlice(flipped)
|
||||
return flipped
|
||||
}
|
||||
305
roaring/naive_test.go
Normal file
305
roaring/naive_test.go
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// 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 roaring
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSortSlice(t *testing.T) {
|
||||
a := []uint64{1, 3, 2, 8, 5, 21, 13}
|
||||
sortSlice(a)
|
||||
if !reflect.DeepEqual(a, []uint64{1, 2, 3, 5, 8, 13, 21}) {
|
||||
t.Fatalf("unexpected sorting: %v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSliceDuplicates(t *testing.T) {
|
||||
a := []uint64{2, 3, 2, 1, 2, 5, 8, 5, 13, 3, 2, 5, 144}
|
||||
a = removeSliceDuplicates(a)
|
||||
|
||||
if !reflect.DeepEqual(a, []uint64{1, 2, 3, 5, 8, 13, 144}) {
|
||||
t.Fatalf("unexpected values: %v", a)
|
||||
}
|
||||
|
||||
a = append(a, 21, 8, 3, 3, 5, 5, 1, 34, 21, 21)
|
||||
a = removeSliceDuplicates(a)
|
||||
|
||||
if !reflect.DeepEqual(a, []uint64{1, 2, 3, 5, 8, 13, 21, 34, 144}) {
|
||||
t.Fatalf("unexpected values: %v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersectSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
b := []uint64{2, 1, 9, 5, 12}
|
||||
c := intersectSlice(a, b)
|
||||
|
||||
if !reflect.DeepEqual(c, []uint64{1, 5, 9}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if !reflect.DeepEqual(c, intersectSlice(b, a)) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if !reflect.DeepEqual(c, intersectSlice(c, a)) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnionSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
b := []uint64{2, 1, 9, 5, 12}
|
||||
c := unionSlice(a, b)
|
||||
|
||||
if !reflect.DeepEqual(c, []uint64{1, 2, 4, 5, 9, 12, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if !reflect.DeepEqual(c, unionSlice(b, a)) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if !reflect.DeepEqual(c, unionSlice(c, a)) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxInSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
v := maxInSlice(a)
|
||||
if uint64(24) != v {
|
||||
t.Fatalf("expected %v, but got %v", uint64(24), v)
|
||||
}
|
||||
|
||||
for i := uint64(1000); i <= uint64(100000); i++ {
|
||||
a = append(a, i)
|
||||
if v = maxInSlice(a); v != i {
|
||||
t.Fatalf("expected %v, but got %v", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifferenceSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
b := []uint64{2, 1, 9, 5, 12}
|
||||
|
||||
c := differenceSlice(a, b)
|
||||
if !reflect.DeepEqual(c, []uint64{4, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
|
||||
c = differenceSlice(b, a)
|
||||
if !reflect.DeepEqual(c, []uint64{2, 12}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
|
||||
c = differenceSlice(a, a)
|
||||
if !reflect.DeepEqual(c, []uint64(nil)) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
b := []uint64{2, 1, 9, 5, 12}
|
||||
c := xorSlice(a, b)
|
||||
|
||||
if !reflect.DeepEqual(c, []uint64{2, 4, 12, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if !reflect.DeepEqual(c, xorSlice(b, a)) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if !reflect.DeepEqual(xorSlice(c, a), []uint64{1, 2, 5, 9, 12}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if !reflect.DeepEqual(xorSlice(c, b), []uint64{1, 4, 5, 9, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShiftSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
|
||||
c := shiftSlice(a, 12)
|
||||
if !reflect.DeepEqual(c, []uint64{13, 16, 17, 21, 25, 36}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
|
||||
c = shiftSlice(a, 0)
|
||||
if !reflect.DeepEqual(c, []uint64{1, 4, 5, 9, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForEachInSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
c := make([]uint64, 0)
|
||||
|
||||
forEachInSlice(a, func(v uint64) {
|
||||
c = append(c, v+1)
|
||||
})
|
||||
if !reflect.DeepEqual(c, []uint64{2, 5, 10, 6, 25, 14}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForEachInRangeSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
c := make([]uint64, 0)
|
||||
|
||||
forEachInRangeSlice(a, uint64(3), uint64(12), func(v uint64) {
|
||||
c = append(c, v+1)
|
||||
})
|
||||
if !reflect.DeepEqual(c, []uint64{5, 10, 6}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainedInSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
|
||||
c := uint64(4)
|
||||
idx, found := containedInSlice(a, c)
|
||||
if !found {
|
||||
t.Fatalf("%v should be in %v", c, a)
|
||||
}
|
||||
if a[idx] != c {
|
||||
t.Fatalf("%v is not at position %v", c, idx)
|
||||
}
|
||||
|
||||
c = uint64(12)
|
||||
idx, found = containedInSlice(a, c)
|
||||
if found {
|
||||
t.Fatalf("%v should not be in %v", c, a)
|
||||
}
|
||||
if idx != -1 {
|
||||
t.Fatalf("expected %v, got %v", -1, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddNToSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
b := []uint64{2, 1, 9, 5, 12}
|
||||
|
||||
c, changed := addNToSlice(a, b...)
|
||||
if !reflect.DeepEqual(c, []uint64{1, 2, 4, 5, 9, 12, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
if changed != 2 {
|
||||
t.Fatalf("changes expected %v, got %v", 2, changed)
|
||||
}
|
||||
|
||||
c, changed = addNToSlice(b, a...)
|
||||
if !reflect.DeepEqual(c, []uint64{1, 2, 4, 5, 9, 12, 13, 24}) {
|
||||
t.Fatalf("%v and %v should be the same", c, a)
|
||||
}
|
||||
if changed != 3 {
|
||||
t.Fatalf("changes expected %v, got %v", 3, changed)
|
||||
}
|
||||
|
||||
c, changed = addNToSlice(a, a...)
|
||||
if !reflect.DeepEqual(c, []uint64{1, 4, 5, 9, 13, 24}) {
|
||||
t.Fatalf("%v and %v should be the same", c, a)
|
||||
}
|
||||
if changed != 0 {
|
||||
t.Fatalf("changes expected %v, got %v", 0, changed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveNFromSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
b := []uint64{2, 1, 9, 5, 12}
|
||||
|
||||
c, changed := removeNFromSlice(a, b...)
|
||||
if !reflect.DeepEqual(c, []uint64{4, 13, 24}) {
|
||||
t.Fatalf("%v and %v should be the same", c, []uint64{4, 13, 24})
|
||||
}
|
||||
if changed != 3 {
|
||||
t.Fatalf("%v changes expected, got %v", 3, changed)
|
||||
}
|
||||
|
||||
c, changed = removeNFromSlice(b, a...)
|
||||
if !reflect.DeepEqual(c, []uint64{2, 12}) {
|
||||
t.Fatalf("%v and %v should be the same", c, []uint64{2, 12})
|
||||
}
|
||||
if changed != 3 {
|
||||
t.Fatalf("%v changes expected, got %v", 3, changed)
|
||||
}
|
||||
|
||||
c, changed = removeNFromSlice(a, a...)
|
||||
if !reflect.DeepEqual(c, []uint64(nil)) {
|
||||
t.Fatalf("%v and %v should be the same", c, a)
|
||||
}
|
||||
if changed != 6 {
|
||||
t.Fatalf("%v changes expected, got %v", 0, changed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountRangeSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
|
||||
c := countRangeSlice(a, uint64(3), uint64(12))
|
||||
if c != 3 {
|
||||
t.Fatalf("expected %v, got %v", 3, c)
|
||||
}
|
||||
|
||||
c = countRangeSlice(a, uint64(0), uint64(25))
|
||||
if c != 6 {
|
||||
t.Fatalf("expected %v, got %v", 6, c)
|
||||
}
|
||||
|
||||
c = countRangeSlice(a, uint64(12), uint64(4))
|
||||
if c != 0 {
|
||||
t.Fatalf("expected %v, got %v", 0, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
|
||||
c := rangeSlice(a, uint64(3), uint64(12))
|
||||
if !reflect.DeepEqual(c, []uint64{4, 5, 9}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
|
||||
c = rangeSlice(a, uint64(0), uint64(25))
|
||||
if !reflect.DeepEqual(c, []uint64{1, 4, 5, 9, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
|
||||
c = rangeSlice(a, uint64(5), uint64(5))
|
||||
if !reflect.DeepEqual(c, []uint64(nil)) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlipSlice(t *testing.T) {
|
||||
a := []uint64{1, 4, 9, 5, 24, 13}
|
||||
|
||||
c := flipSlice(a, uint64(3), uint64(12))
|
||||
if !reflect.DeepEqual(c, []uint64{1, 3, 6, 7, 8, 10, 11, 12, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
|
||||
c = flipSlice(a, uint64(13), uint64(12))
|
||||
if !reflect.DeepEqual(c, []uint64{1, 4, 5, 9, 13, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
|
||||
c = flipSlice(a, uint64(9), uint64(13))
|
||||
if !reflect.DeepEqual(c, []uint64{1, 4, 5, 10, 11, 12, 24}) {
|
||||
t.Fatalf("unexpected values: %v", c)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue