Update dependencies

This commit is contained in:
bluepython508
2025-04-09 01:00:12 +01:00
parent f0641ffd6e
commit 5a9cfc022c
882 changed files with 68930 additions and 24201 deletions

View File

@@ -0,0 +1,326 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// Package bitset implements bitsets, a mapping
// between non-negative integers and boolean values.
//
// Studied [github.com/bits-and-blooms/bitset] inside out
// and rewrote needed parts from scratch for this project.
//
// This implementation is smaller and faster as the more
// general [github.com/bits-and-blooms/bitset].
//
// All functions can be inlined!
//
// can inline BitSet.Set with cost 63
// can inline BitSet.Clear with cost 24
// can inline BitSet.Test with cost 26
// can inline BitSet.Rank0 with cost 66
// can inline BitSet.Clone with cost 7
// can inline BitSet.Compact with cost 35
// can inline BitSet.FirstSet with cost 25
// can inline BitSet.NextSet with cost 71
// can inline BitSet.AsSlice with cost 50
// can inline BitSet.All with cost 62
// can inline BitSet.IntersectsAny with cost 42
// can inline BitSet.IntersectionTop with cost 56
// can inline BitSet.IntersectionCardinality with cost 35
// can inline (*BitSet).InPlaceIntersection with cost 71
// can inline (*BitSet).InPlaceUnion with cost 77
// can inline BitSet.Size with cost 16
// can inline popcount with cost 12
// can inline popcountAnd with cost 30
package bitset
import (
"math/bits"
)
// A BitSet is a slice of words. This is an internal package
// with a wide open public API.
type BitSet []uint64
// xIdx calculates the index of i in a []uint64
// func wIdx(i uint) int {
// return int(i >> 6) // like (i / 64) but faster
// }
// bIdx calculates the index of i in a `uint64`
// func bIdx(i uint) uint {
// return i & 63 // like (i % 64) but faster
// }
//
// just as an explanation of the expressions,
//
// i>>6 or i<<6 and i&63
//
// not factored out as functions to make most of the methods
// inlineable with minimal costs.
// Set bit i to 1, the capacity of the bitset is increased accordingly.
func (b BitSet) Set(i uint) BitSet {
// grow?
if i >= uint(len(b)<<6) {
words := int((i + 64) >> 6)
switch {
case b == nil:
b = make([]uint64, words)
case cap(b) >= words:
b = b[:words]
default:
// be exact, don't use append!
// max 512 prefixes/node (8*uint64), and a cache line has 64 Bytes
newset := make([]uint64, words)
copy(newset, b)
b = newset
}
}
b[i>>6] |= 1 << (i & 63)
return b
}
// Clear bit i to 0.
func (b BitSet) Clear(i uint) BitSet {
if x := int(i >> 6); x < len(b) {
b[x] &^= 1 << (i & 63)
}
return b
}
// Test if bit i is set.
func (b BitSet) Test(i uint) (ok bool) {
if x := int(i >> 6); x < len(b) {
return b[x]&(1<<(i&63)) != 0
}
return
}
// Clone this BitSet, returning a new BitSet that has the same bits set.
func (b BitSet) Clone() BitSet {
return append(b[:0:0], b...)
}
// Compact, preserve all set bits, while minimizing memory usage.
func (b BitSet) Compact() BitSet {
last := len(b) - 1
// find last word with at least one bit set.
for ; last >= 0; last-- {
if b[last] != 0 {
b = b[: last+1 : last+1]
return b
}
}
// BitSet was empty, shrink to nil
return nil
}
// FirstSet returns the first bit set along with an ok code.
func (b BitSet) FirstSet() (uint, bool) {
for x, word := range b {
if word != 0 {
return uint(x<<6 + bits.TrailingZeros64(word)), true
}
}
return 0, false
}
// NextSet returns the next bit set from the specified index,
// including possibly the current index along with an ok code.
func (b BitSet) NextSet(i uint) (uint, bool) {
x := int(i >> 6)
if x >= len(b) {
return 0, false
}
// process the first (maybe partial) word
first := b[x] >> (i & 63) // i % 64
if first != 0 {
return i + uint(bits.TrailingZeros64(first)), true
}
// process the following words until next bit is set
// x < len(b), no out-of-bounds panic in following slice expression
x++
for j, word := range b[x:] {
if word != 0 {
return uint((x+j)<<6 + bits.TrailingZeros64(word)), true
}
}
return 0, false
}
// AsSlice returns all set bits as slice of uint without
// heap allocations.
//
// This is faster than All, but also more dangerous,
// it panics if the capacity of buf is < b.Size()
func (b BitSet) AsSlice(buf []uint) []uint {
buf = buf[:cap(buf)] // len = cap
size := 0
for idx, word := range b {
for ; word != 0; size++ {
// panics if capacity of buf is exceeded.
buf[size] = uint(idx<<6 + bits.TrailingZeros64(word))
// clear the rightmost set bit
word &= word - 1
}
}
buf = buf[:size]
return buf
}
// All returns all set bits. This is simpler but slower than AsSlice.
func (b BitSet) All() []uint {
buf := make([]uint, b.Size())
slot := 0
for idx, word := range b {
for word != 0 {
buf[slot] = uint(idx<<6 + bits.TrailingZeros64(word))
slot++
// clear the rightmost set bit
word &= word - 1
}
}
return buf
}
// IntersectsAny returns true if the intersection of base set with the compare set
// is not the empty set.
func (b BitSet) IntersectsAny(c BitSet) bool {
i := min(len(b), len(c)) - 1
// bounds check eliminated (BCE)
for ; i >= 0 && i < len(b) && i < len(c); i-- {
if b[i]&c[i] != 0 {
return true
}
}
return false
}
// IntersectionTop computes the intersection of base set with the compare set.
// If the result set isn't empty, it returns the top most set bit and true.
func (b BitSet) IntersectionTop(c BitSet) (top uint, ok bool) {
i := min(len(b), len(c)) - 1
// bounds check eliminated (BCE)
for ; i >= 0 && i < len(b) && i < len(c); i-- {
if word := b[i] & c[i]; word != 0 {
return uint(i<<6+bits.Len64(word)) - 1, true
}
}
return
}
// IntersectionCardinality computes the popcount of the intersection.
func (b BitSet) IntersectionCardinality(c BitSet) int {
return popcntAnd(b, c)
}
// InPlaceIntersection overwrites and computes the intersection of
// base set with the compare set. This is the BitSet equivalent of & (and).
// If len(c) > len(b), new memory is allocated.
func (b *BitSet) InPlaceIntersection(c BitSet) {
// bounds check eliminated, range until minLen(b,c)
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] &= c[i]
}
// b >= c
if len(*b) >= len(c) {
// bounds check eliminated
for i := len(c); i < len(*b); i++ {
(*b)[i] = 0
}
return
}
// b < c
newset := make([]uint64, len(c))
copy(newset, *b)
*b = newset
}
// InPlaceUnion creates the destructive union of base set with compare set.
// This is the BitSet equivalent of | (or).
// If len(c) > len(b), new memory is allocated.
func (b *BitSet) InPlaceUnion(c BitSet) {
// b >= c
if len(*b) >= len(c) {
// bounds check eliminated
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] |= c[i]
}
return
}
// b < c
newset := make([]uint64, len(c))
copy(newset, *b)
*b = newset
// bounds check eliminated
for i := 0; i < len(*b) && i < len(c); i++ {
(*b)[i] |= c[i]
}
}
// Size (number of set bits).
func (b BitSet) Size() int {
return popcntSlice(b)
}
// Rank0 is equal to Rank(i) - 1
//
// With inlined popcount to make Rank0 itself inlineable.
func (b BitSet) Rank0(i uint) (rnk int) {
// Rank count is inclusive
i++
if wordIdx := int(i >> 6); wordIdx >= len(b) {
// inlined popcount, whole slice
for _, x := range b {
rnk += bits.OnesCount64(x)
}
} else {
// inlined popcount, partial slice ...
for _, x := range b[:wordIdx] {
rnk += bits.OnesCount64(x)
}
// ... plus partial word?
if bitsIdx := i & 63; bitsIdx != 0 {
rnk += bits.OnesCount64(b[wordIdx] << (64 - bitsIdx))
}
}
// correct for offset by one
return rnk - 1
}
// popcntSlice
func popcntSlice(s []uint64) (cnt int) {
for _, x := range s {
// count all the bits set in slice.
cnt += bits.OnesCount64(x)
}
return
}
// popcntAnd
func popcntAnd(s, m []uint64) (cnt int) {
for j := 0; j < len(s) && j < len(m); j++ {
// words are bitwise & followed by popcount.
cnt += bits.OnesCount64(s[j] & m[j])
}
return
}

View File

@@ -0,0 +1,160 @@
// Copyright (c) 2024 Karl Gaissmaier
// SPDX-License-Identifier: MIT
// package sparse implements a generic sparse array
// with popcount compression.
package sparse
import (
"github.com/gaissmai/bart/internal/bitset"
)
// Array, a generic implementation of a sparse array
// with popcount compression and payload T.
type Array[T any] struct {
bitset.BitSet
Items []T
}
// Get the value at i from sparse array.
//
// example: Array.Get(5) -> Array.Items[1]
//
// ⬇
// BitSet: [0|0|1|0|0|1|0|1|...] <- 3 bits set
// Items: [*|*|*] <- len(Items) = 3
// ⬆
//
// BitSet.Test(5): true
// BitSet.popcount(5): 2, for interval [0,5]
// BitSet.Rank0(5): 1, equal popcount(5)-1
func (s *Array[T]) Get(i uint) (value T, ok bool) {
if s.Test(i) {
return s.Items[s.Rank0(i)], true
}
return
}
// MustGet, use it only after a successful test
// or the behavior is undefined, maybe it panics.
func (s *Array[T]) MustGet(i uint) T {
return s.Items[s.Rank0(i)]
}
// UpdateAt or set the value at i via callback. The new value is returned
// and true if the value was already present.
func (s *Array[T]) UpdateAt(i uint, cb func(T, bool) T) (newValue T, wasPresent bool) {
var rank0 int
// if already set, get current value
var oldValue T
if wasPresent = s.Test(i); wasPresent {
rank0 = s.Rank0(i)
oldValue = s.Items[rank0]
}
// callback function to get updated or new value
newValue = cb(oldValue, wasPresent)
// already set, update and return value
if wasPresent {
s.Items[rank0] = newValue
return newValue, wasPresent
}
// new value, insert into bitset ...
s.BitSet = s.Set(i)
// bitset has changed, recalc rank
rank0 = s.Rank0(i)
// ... and insert value into slice
s.insertItem(rank0, newValue)
return newValue, wasPresent
}
// Len returns the number of items in sparse array.
func (s *Array[T]) Len() int {
return len(s.Items)
}
// Copy returns a shallow copy of the Array.
// The elements are copied using assignment, this is no deep clone.
func (s *Array[T]) Copy() *Array[T] {
if s == nil {
return nil
}
return &Array[T]{
BitSet: s.BitSet.Clone(),
Items: append(s.Items[:0:0], s.Items...),
}
}
// InsertAt a value at i into the sparse array.
// If the value already exists, overwrite it with val and return true.
func (s *Array[T]) InsertAt(i uint, value T) (exists bool) {
// slot exists, overwrite value
if s.Len() != 0 && s.Test(i) {
s.Items[s.Rank0(i)] = value
return true
}
// new, insert into bitset ...
s.BitSet = s.Set(i)
// ... and slice
s.insertItem(s.Rank0(i), value)
return false
}
// DeleteAt a value at i from the sparse array, zeroes the tail.
func (s *Array[T]) DeleteAt(i uint) (value T, exists bool) {
if s.Len() == 0 || !s.Test(i) {
return
}
rank0 := s.Rank0(i)
value = s.Items[rank0]
// delete from slice
s.deleteItem(rank0)
// delete from bitset
s.BitSet = s.Clear(i)
return value, true
}
// insertItem inserts the item at index i, shift the rest one pos right
//
// It panics if i is out of range.
func (s *Array[T]) insertItem(i int, item T) {
if len(s.Items) < cap(s.Items) {
s.Items = s.Items[:len(s.Items)+1] // fast resize, no alloc
} else {
var zero T
s.Items = append(s.Items, zero) // append one item, mostly enlarge cap by more than one item
}
copy(s.Items[i+1:], s.Items[i:])
s.Items[i] = item
}
// deleteItem at index i, shift the rest one pos left and clears the tail item
//
// It panics if i is out of range.
func (s *Array[T]) deleteItem(i int) {
var zero T
nl := len(s.Items) - 1 // new len
copy(s.Items[i:], s.Items[i+1:]) // overwrite item at [i]
s.Items[nl] = zero // clear the tail item
s.Items = s.Items[:nl] // new len, keep cap is unchanged
}