featurebase/roaring/naive.go
2022-09-02 13:23:39 -07:00

309 lines
7.2 KiB
Go

// Copyright 2022 Molecula Corp. (DBA FeatureBase).
// SPDX-License-Identifier: Apache-2.0
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
}