mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
Merge branch 'master' into tests
This commit is contained in:
commit
f54bb5ed2b
6 changed files with 1118 additions and 3 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
|
||||
}
|
||||
459
roaring/naive_test.go
Normal file
459
roaring/naive_test.go
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
// 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) {
|
||||
tests := []struct{ a, expected []uint64 }{
|
||||
{
|
||||
a: []uint64{1, 3, 2, 8, 5, 21, 13},
|
||||
expected: []uint64{1, 2, 3, 5, 8, 13, 21},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
sortSlice(test.a)
|
||||
if !reflect.DeepEqual(test.a, test.expected) {
|
||||
t.Fatalf("unexpected sorting: %v", test.a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSliceDuplicates(t *testing.T) {
|
||||
tests := []struct{ a, expected []uint64 }{
|
||||
{
|
||||
a: []uint64{2, 3, 2, 1, 2, 5, 8, 5, 13, 3, 2, 5, 144},
|
||||
expected: []uint64{1, 2, 3, 5, 8, 13, 144},
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 3, 2, 1, 2, 5, 8, 5, 13, 3, 2, 5, 144, 21, 8, 3, 3, 5, 5, 1, 34, 21, 21},
|
||||
expected: []uint64{1, 2, 3, 5, 8, 13, 21, 34, 144},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := removeSliceDuplicates(test.a)
|
||||
if !reflect.DeepEqual(got, test.expected) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersectSlice(t *testing.T) {
|
||||
tests := []struct{ a, b, expected []uint64 }{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{2, 1, 9, 5, 12},
|
||||
expected: []uint64{1, 5, 9},
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 1, 9, 5, 12},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{1, 5, 9},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{1, 5, 9},
|
||||
expected: []uint64{1, 5, 9},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := intersectSlice(test.a, test.b)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnionSlice(t *testing.T) {
|
||||
tests := []struct{ a, b, expected []uint64 }{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{2, 1, 9, 5, 12},
|
||||
expected: []uint64{1, 2, 4, 5, 9, 12, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 1, 9, 5, 12},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{1, 2, 4, 5, 9, 12, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{1, 5, 9},
|
||||
expected: []uint64{1, 4, 5, 9, 13, 24},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := unionSlice(test.a, test.b)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
tests := []struct{ a, b, expected []uint64 }{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{2, 1, 9, 5, 12},
|
||||
expected: []uint64{4, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 1, 9, 5, 12},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{2, 12},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := differenceSlice(test.a, test.b)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorSlice(t *testing.T) {
|
||||
tests := []struct{ a, b, expected []uint64 }{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{2, 1, 9, 5, 12},
|
||||
expected: []uint64{2, 4, 12, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 1, 9, 5, 12},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{2, 4, 12, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 4, 12, 13, 24},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{1, 2, 5, 9, 12},
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 4, 12, 13, 24},
|
||||
b: []uint64{1, 2, 5, 9, 12},
|
||||
expected: []uint64{1, 4, 5, 9, 13, 24},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := xorSlice(test.a, test.b)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShiftSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []uint64
|
||||
shift int
|
||||
expected []uint64
|
||||
}{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
shift: 12,
|
||||
expected: []uint64{13, 16, 17, 21, 25, 36},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
shift: 0,
|
||||
expected: []uint64{1, 4, 5, 9, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
shift: 1,
|
||||
expected: []uint64{2, 5, 6, 10, 14, 25},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := shiftSlice(test.a, test.shift)
|
||||
|
||||
if !reflect.DeepEqual(got, test.expected) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
tests := []struct {
|
||||
a []uint64
|
||||
c uint64
|
||||
index int
|
||||
found bool
|
||||
}{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
c: uint64(4),
|
||||
index: 1,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
c: uint64(12),
|
||||
index: -1,
|
||||
found: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
idx, found := containedInSlice(test.a, test.c)
|
||||
if found != test.found {
|
||||
t.Fatalf("expected value of found: %v, got %v", test.found, found)
|
||||
}
|
||||
if idx != test.index {
|
||||
t.Fatalf("expected index %v, got %v", test.index, idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddNToSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []uint64
|
||||
b []uint64
|
||||
expected []uint64
|
||||
changed int
|
||||
}{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{2, 1, 9, 5, 12},
|
||||
expected: []uint64{1, 2, 4, 5, 9, 12, 13, 24},
|
||||
changed: 2,
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 1, 9, 5, 12},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{1, 2, 4, 5, 9, 12, 13, 24},
|
||||
changed: 3,
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{1, 4, 5, 9, 13, 24},
|
||||
changed: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got, changed := addNToSlice(test.a, test.b...)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
t.Fatalf("expected slices %v, got %v", test.expected, got)
|
||||
}
|
||||
if changed != test.changed {
|
||||
t.Fatalf("expected changed %v, got %v", test.changed, changed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveNFromSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []uint64
|
||||
b []uint64
|
||||
expected []uint64
|
||||
changed int
|
||||
}{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{2, 1, 9, 5, 12},
|
||||
expected: []uint64{4, 13, 24},
|
||||
changed: 3,
|
||||
},
|
||||
{
|
||||
a: []uint64{2, 1, 9, 5, 12},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64{2, 12},
|
||||
changed: 3,
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
b: []uint64{1, 4, 9, 5, 24, 13},
|
||||
expected: []uint64(nil),
|
||||
changed: 6,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got, changed := removeNFromSlice(test.a, test.b...)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
t.Fatalf("expected slices %v, got %v", test.expected, got)
|
||||
}
|
||||
if changed != test.changed {
|
||||
t.Fatalf("expected changed %v, got %v", test.changed, changed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountRangeSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []uint64
|
||||
start uint64
|
||||
end uint64
|
||||
expected uint64
|
||||
}{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(3),
|
||||
end: uint64(12),
|
||||
expected: uint64(3),
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(0),
|
||||
end: uint64(25),
|
||||
expected: uint64(6),
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(12),
|
||||
end: uint64(4),
|
||||
expected: uint64(0),
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(4),
|
||||
end: uint64(4),
|
||||
expected: uint64(0),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := countRangeSlice(test.a, test.start, test.end)
|
||||
if got != test.expected {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []uint64
|
||||
start uint64
|
||||
end uint64
|
||||
expected []uint64
|
||||
}{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(3),
|
||||
end: uint64(12),
|
||||
expected: []uint64{4, 5, 9},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(0),
|
||||
end: uint64(25),
|
||||
expected: []uint64{1, 4, 5, 9, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(5),
|
||||
end: uint64(5),
|
||||
expected: []uint64(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := rangeSlice(test.a, test.start, test.end)
|
||||
if !reflect.DeepEqual(got, test.expected) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlipSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []uint64
|
||||
start uint64
|
||||
end uint64
|
||||
expected []uint64
|
||||
}{
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(3),
|
||||
end: uint64(12),
|
||||
expected: []uint64{1, 3, 6, 7, 8, 10, 11, 12, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(13),
|
||||
end: uint64(12),
|
||||
expected: []uint64{1, 4, 5, 9, 13, 24},
|
||||
},
|
||||
{
|
||||
a: []uint64{1, 4, 9, 5, 24, 13},
|
||||
start: uint64(9),
|
||||
end: uint64(13),
|
||||
expected: []uint64{1, 4, 5, 10, 11, 12, 24},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := flipSlice(test.a, test.start, test.end)
|
||||
if !reflect.DeepEqual(got, test.expected) {
|
||||
t.Fatalf("expected %v, got %v", test.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -431,7 +431,7 @@ func (b *Bitmap) Size() int {
|
|||
|
||||
// CountRange returns the number of bits set between [start, end).
|
||||
func (b *Bitmap) CountRange(start, end uint64) (n uint64) {
|
||||
if roaringParanoia {
|
||||
if roaringSentinel {
|
||||
if start > end {
|
||||
panic(fmt.Sprintf("counting in range but %v > %v", start, end))
|
||||
}
|
||||
|
|
@ -494,7 +494,7 @@ func (b *Bitmap) Slice() []uint64 {
|
|||
|
||||
// SliceRange returns a slice of integers between [start, end).
|
||||
func (b *Bitmap) SliceRange(start, end uint64) []uint64 {
|
||||
if roaringParanoia {
|
||||
if roaringSentinel {
|
||||
if start > end {
|
||||
panic(fmt.Sprintf("getting slice in range but %v > %v", start, end))
|
||||
}
|
||||
|
|
@ -1271,7 +1271,7 @@ func (b *Bitmap) Check() error {
|
|||
|
||||
// Flip performs a logical negate of the bits in the range [start,end].
|
||||
func (b *Bitmap) Flip(start, end uint64) *Bitmap {
|
||||
if roaringParanoia {
|
||||
if roaringSentinel {
|
||||
if start > end {
|
||||
panic(fmt.Sprintf("flipping in range but %v > %v", start, end))
|
||||
}
|
||||
|
|
|
|||
19
roaring/roaring_nop_sentinel.go
Normal file
19
roaring/roaring_nop_sentinel.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// 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.
|
||||
|
||||
// +build !roaringsentinel
|
||||
|
||||
package roaring
|
||||
|
||||
const roaringSentinel = false
|
||||
19
roaring/roaring_sentinel.go
Normal file
19
roaring/roaring_sentinel.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// 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.
|
||||
|
||||
// +build roaringsentinel
|
||||
|
||||
package roaring
|
||||
|
||||
const roaringSentinel = true
|
||||
Loading…
Add table
Reference in a new issue