feat: add heap, linked, sorting, zero - remove utils dependency

- Add Zero[T any]() generic function for zero values
- Add IsSortedBytes for byte slice sorting checks
- Add heap/queue.go with priority Queue[T] implementation
- Add heap/map.go with indexed Map[K,V] for heap operations
- Add linked/list.go with doubly-linked List[T]
- Add linked/hashmap.go with ordered HashMap[K,V]
- Update sampleable_set.go to use local luxmath.Zero[T]()
- Update averager_heap.go to use github.com/luxfi/math/heap
- Update safe_math.go to use luxmath.Zero[T]()
- Remove github.com/luxfi/utils dependency entirely
This commit is contained in:
Zach Kelling
2025-12-24 15:06:41 -08:00
parent 086468c3ca
commit 107c43637e
11 changed files with 648 additions and 30 deletions
-2
View File
@@ -6,14 +6,12 @@ require (
github.com/luxfi/geth v1.16.52 github.com/luxfi/geth v1.16.52
github.com/luxfi/ids v1.2.4 github.com/luxfi/ids v1.2.4
github.com/luxfi/sampler v1.0.0 github.com/luxfi/sampler v1.0.0
github.com/luxfi/utils v1.0.0
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
) )
require ( require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gorilla/rpc v1.2.1 // indirect
github.com/luxfi/crypto v1.17.25 // indirect github.com/luxfi/crypto v1.17.25 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-4
View File
@@ -1,7 +1,5 @@
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -14,8 +12,6 @@ github.com/luxfi/ids v1.2.4 h1:e0OaeSI6xWjS9JnxxxfvCCs9HHDUrwDc3M9ctIhjcDI=
github.com/luxfi/ids v1.2.4/go.mod h1:HwvRJSNbuZS+u+0JqeHfPX2S13iFyLCnfGxjBCmt244= github.com/luxfi/ids v1.2.4/go.mod h1:HwvRJSNbuZS+u+0JqeHfPX2S13iFyLCnfGxjBCmt244=
github.com/luxfi/sampler v1.0.0 h1:k8Sf6otW83w4pQp0jXLA+g3J/joB7w7SqXQsWmNTOV0= github.com/luxfi/sampler v1.0.0 h1:k8Sf6otW83w4pQp0jXLA+g3J/joB7w7SqXQsWmNTOV0=
github.com/luxfi/sampler v1.0.0/go.mod h1:f96/ozlj9vFfZj+akLtrHn4VpulQahwB+MQQhpeIekk= github.com/luxfi/sampler v1.0.0/go.mod h1:f96/ozlj9vFfZj+akLtrHn4VpulQahwB+MQQhpeIekk=
github.com/luxfi/utils v1.0.0 h1:eibxyw/xkwnB9KRSmYvKLbdLTUElyfJpxLIBeTBKqlU=
github.com/luxfi/utils v1.0.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+132
View File
@@ -0,0 +1,132 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package heap
import (
"container/heap"
luxmath "github.com/luxfi/math"
)
var _ heap.Interface = (*indexedQueue[int, int])(nil)
func MapValues[K comparable, V any](m Map[K, V]) []V {
result := make([]V, 0, m.Len())
for _, e := range m.queue.entries {
result = append(result, e.v)
}
return result
}
// NewMap returns a heap without duplicates ordered by its values
func NewMap[K comparable, V any](less func(a, b V) bool) Map[K, V] {
return Map[K, V]{
queue: &indexedQueue[K, V]{
queue: queue[entry[K, V]]{
less: func(a, b entry[K, V]) bool {
return less(a.v, b.v)
},
},
index: make(map[K]int),
},
}
}
type Map[K comparable, V any] struct {
queue *indexedQueue[K, V]
}
// Push returns the evicted previous value if present
func (m *Map[K, V]) Push(k K, v V) (V, bool) {
if i, ok := m.queue.index[k]; ok {
prev := m.queue.entries[i]
m.queue.entries[i].v = v
heap.Fix(m.queue, i)
return prev.v, true
}
heap.Push(m.queue, entry[K, V]{k: k, v: v})
return luxmath.Zero[V](), false
}
func (m *Map[K, V]) Pop() (K, V, bool) {
if m.Len() == 0 {
return luxmath.Zero[K](), luxmath.Zero[V](), false
}
popped := heap.Pop(m.queue).(entry[K, V])
return popped.k, popped.v, true
}
func (m *Map[K, V]) Peek() (K, V, bool) {
if m.Len() == 0 {
return luxmath.Zero[K](), luxmath.Zero[V](), false
}
entry := m.queue.entries[0]
return entry.k, entry.v, true
}
func (m *Map[K, V]) Len() int {
return m.queue.Len()
}
func (m *Map[K, V]) Remove(k K) (V, bool) {
if i, ok := m.queue.index[k]; ok {
removed := heap.Remove(m.queue, i).(entry[K, V])
return removed.v, true
}
return luxmath.Zero[V](), false
}
func (m *Map[K, V]) Contains(k K) bool {
_, ok := m.queue.index[k]
return ok
}
func (m *Map[K, V]) Get(k K) (V, bool) {
if i, ok := m.queue.index[k]; ok {
got := m.queue.entries[i]
return got.v, true
}
return luxmath.Zero[V](), false
}
func (m *Map[K, V]) Fix(k K) {
if i, ok := m.queue.index[k]; ok {
heap.Fix(m.queue, i)
}
}
type indexedQueue[K comparable, V any] struct {
queue[entry[K, V]]
index map[K]int
}
func (h *indexedQueue[K, V]) Swap(i, j int) {
h.entries[i], h.entries[j] = h.entries[j], h.entries[i]
h.index[h.entries[i].k], h.index[h.entries[j].k] = i, j
}
func (h *indexedQueue[K, V]) Push(x any) {
entry := x.(entry[K, V])
h.entries = append(h.entries, entry)
h.index[entry.k] = len(h.index)
}
func (h *indexedQueue[K, V]) Pop() any {
end := len(h.entries) - 1
popped := h.entries[end]
h.entries[end] = entry[K, V]{}
h.entries = h.entries[:end]
delete(h.index, popped.k)
return popped
}
type entry[K any, V any] struct {
k K
v V
}
+94
View File
@@ -0,0 +1,94 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package heap
import (
"container/heap"
luxmath "github.com/luxfi/math"
)
var _ heap.Interface = (*queue[int])(nil)
// NewQueue returns an empty heap. See QueueOf for more.
func NewQueue[T any](less func(a, b T) bool) Queue[T] {
return QueueOf(less)
}
// QueueOf returns a heap containing entries ordered by less.
func QueueOf[T any](less func(a, b T) bool, entries ...T) Queue[T] {
q := Queue[T]{
queue: &queue[T]{
entries: make([]T, len(entries)),
less: less,
},
}
copy(q.queue.entries, entries)
heap.Init(q.queue)
return q
}
type Queue[T any] struct {
queue *queue[T]
}
func (q *Queue[T]) Len() int {
return len(q.queue.entries)
}
func (q *Queue[T]) Push(t T) {
heap.Push(q.queue, t)
}
func (q *Queue[T]) Pop() (T, bool) {
if q.Len() == 0 {
return luxmath.Zero[T](), false
}
return heap.Pop(q.queue).(T), true
}
func (q *Queue[T]) Peek() (T, bool) {
if q.Len() == 0 {
return luxmath.Zero[T](), false
}
return q.queue.entries[0], true
}
func (q *Queue[T]) Fix(i int) {
heap.Fix(q.queue, i)
}
type queue[T any] struct {
entries []T
less func(a, b T) bool
}
func (q *queue[T]) Len() int {
return len(q.entries)
}
func (q *queue[T]) Less(i, j int) bool {
return q.less(q.entries[i], q.entries[j])
}
func (q *queue[T]) Swap(i, j int) {
q.entries[i], q.entries[j] = q.entries[j], q.entries[i]
}
func (q *queue[T]) Push(e any) {
q.entries = append(q.entries, e.(T))
}
func (q *queue[T]) Pop() any {
end := len(q.entries) - 1
popped := q.entries[end]
q.entries[end] = luxmath.Zero[T]()
q.entries = q.entries[:end]
return popped
}
+166
View File
@@ -0,0 +1,166 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package linked
import luxmath "github.com/luxfi/math"
type keyValue[K, V any] struct {
key K
value V
}
// Hashmap provides an ordered O(1) mapping from keys to values.
//
// Entries are tracked by insertion order.
type Hashmap[K comparable, V any] struct {
entryMap map[K]*ListElement[keyValue[K, V]]
entryList *List[keyValue[K, V]]
freeList []*ListElement[keyValue[K, V]]
}
func NewHashmap[K comparable, V any]() *Hashmap[K, V] {
return NewHashmapWithSize[K, V](0)
}
func NewHashmapWithSize[K comparable, V any](initialSize int) *Hashmap[K, V] {
lh := &Hashmap[K, V]{
entryMap: make(map[K]*ListElement[keyValue[K, V]], initialSize),
entryList: NewList[keyValue[K, V]](),
freeList: make([]*ListElement[keyValue[K, V]], initialSize),
}
for i := range lh.freeList {
lh.freeList[i] = &ListElement[keyValue[K, V]]{}
}
return lh
}
func (lh *Hashmap[K, V]) Put(key K, value V) {
if e, ok := lh.entryMap[key]; ok {
lh.entryList.MoveToBack(e)
e.Value = keyValue[K, V]{
key: key,
value: value,
}
return
}
var e *ListElement[keyValue[K, V]]
if numFree := len(lh.freeList); numFree > 0 {
numFree--
e = lh.freeList[numFree]
lh.freeList = lh.freeList[:numFree]
} else {
e = &ListElement[keyValue[K, V]]{}
}
e.Value = keyValue[K, V]{
key: key,
value: value,
}
lh.entryMap[key] = e
lh.entryList.PushBack(e)
}
func (lh *Hashmap[K, V]) Get(key K) (V, bool) {
if e, ok := lh.entryMap[key]; ok {
return e.Value.value, true
}
return luxmath.Zero[V](), false
}
func (lh *Hashmap[K, V]) Delete(key K) bool {
e, ok := lh.entryMap[key]
if ok {
lh.remove(e)
}
return ok
}
func (lh *Hashmap[K, V]) Clear() {
for _, e := range lh.entryMap {
lh.remove(e)
}
}
// remove assumes that [e] is currently in the Hashmap.
func (lh *Hashmap[K, V]) remove(e *ListElement[keyValue[K, V]]) {
delete(lh.entryMap, e.Value.key)
lh.entryList.Remove(e)
e.Value = keyValue[K, V]{} // Free the key value pair
lh.freeList = append(lh.freeList, e)
}
func (lh *Hashmap[K, V]) Len() int {
return len(lh.entryMap)
}
func (lh *Hashmap[K, V]) Oldest() (K, V, bool) {
if e := lh.entryList.Front(); e != nil {
return e.Value.key, e.Value.value, true
}
return luxmath.Zero[K](), luxmath.Zero[V](), false
}
func (lh *Hashmap[K, V]) Newest() (K, V, bool) {
if e := lh.entryList.Back(); e != nil {
return e.Value.key, e.Value.value, true
}
return luxmath.Zero[K](), luxmath.Zero[V](), false
}
func (lh *Hashmap[K, V]) NewIterator() *Iterator[K, V] {
return &Iterator[K, V]{lh: lh}
}
// Iterates over the keys and values in a LinkedHashmap from oldest to newest.
// Assumes the underlying LinkedHashmap is not modified while the iterator is in
// use, except to delete elements that have already been iterated over.
type Iterator[K comparable, V any] struct {
lh *Hashmap[K, V]
key K
value V
next *ListElement[keyValue[K, V]]
initialized, exhausted bool
}
func (it *Iterator[K, V]) Next() bool {
// If the iterator has been exhausted, there is no next value.
if it.exhausted {
it.key = luxmath.Zero[K]()
it.value = luxmath.Zero[V]()
it.next = nil
return false
}
// If the iterator was not yet initialized, do it now.
if !it.initialized {
it.initialized = true
oldest := it.lh.entryList.Front()
if oldest == nil {
it.exhausted = true
it.key = luxmath.Zero[K]()
it.value = luxmath.Zero[V]()
it.next = nil
return false
}
it.next = oldest
}
// It's important to ensure that [it.next] is not nil
// by not deleting elements that have not yet been iterated
// over from [it.lh]
it.key = it.next.Value.key
it.value = it.next.Value.value
it.next = it.next.Next() // Next time, return next element
it.exhausted = it.next == nil
return true
}
func (it *Iterator[K, V]) Key() K {
return it.key
}
func (it *Iterator[K, V]) Value() V {
return it.value
}
+217
View File
@@ -0,0 +1,217 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package linked
// ListElement is an element of a linked list.
type ListElement[T any] struct {
next, prev *ListElement[T]
list *List[T]
Value T
}
// Next returns the next element or nil.
func (e *ListElement[T]) Next() *ListElement[T] {
if p := e.next; e.list != nil && p != &e.list.sentinel {
return p
}
return nil
}
// Prev returns the previous element or nil.
func (e *ListElement[T]) Prev() *ListElement[T] {
if p := e.prev; e.list != nil && p != &e.list.sentinel {
return p
}
return nil
}
// List implements a doubly linked list with a sentinel node.
//
// See: https://en.wikipedia.org/wiki/Doubly_linked_list
//
// This datastructure is designed to be an almost complete drop-in replacement
// for the standard library's "container/list".
//
// The primary design change is to remove all memory allocations from the list
// definition. This allows these lists to be used in performance critical paths.
// Additionally the zero value is not useful. Lists must be created with the
// NewList method.
type List[T any] struct {
// sentinel is only used as a placeholder to avoid complex nil checks.
// sentinel.Value is never used.
sentinel ListElement[T]
length int
}
// NewList creates a new doubly linked list.
func NewList[T any]() *List[T] {
l := &List[T]{}
l.sentinel.next = &l.sentinel
l.sentinel.prev = &l.sentinel
l.sentinel.list = l
return l
}
// Len returns the number of elements in l.
func (l *List[_]) Len() int {
return l.length
}
// Front returns the element at the front of l.
// If l is empty, nil is returned.
func (l *List[T]) Front() *ListElement[T] {
if l.length == 0 {
return nil
}
return l.sentinel.next
}
// Back returns the element at the back of l.
// If l is empty, nil is returned.
func (l *List[T]) Back() *ListElement[T] {
if l.length == 0 {
return nil
}
return l.sentinel.prev
}
// Remove removes e from l if e is in l.
func (l *List[T]) Remove(e *ListElement[T]) {
if e.list != l {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil
e.prev = nil
e.list = nil
l.length--
}
// PushFront inserts e at the front of l.
// If e is already in a list, l is not modified.
func (l *List[T]) PushFront(e *ListElement[T]) {
l.insertAfter(e, &l.sentinel)
}
// PushBack inserts e at the back of l.
// If e is already in a list, l is not modified.
func (l *List[T]) PushBack(e *ListElement[T]) {
l.insertAfter(e, l.sentinel.prev)
}
// InsertBefore inserts e immediately before location.
// If e is already in a list, l is not modified.
// If location is not in l, l is not modified.
func (l *List[T]) InsertBefore(e *ListElement[T], location *ListElement[T]) {
if location.list == l {
l.insertAfter(e, location.prev)
}
}
// InsertAfter inserts e immediately after location.
// If e is already in a list, l is not modified.
// If location is not in l, l is not modified.
func (l *List[T]) InsertAfter(e *ListElement[T], location *ListElement[T]) {
if location.list == l {
l.insertAfter(e, location)
}
}
// MoveToFront moves e to the front of l.
// If e is not in l, l is not modified.
func (l *List[T]) MoveToFront(e *ListElement[T]) {
// If e is already at the front of l, there is nothing to do.
if e != l.sentinel.next {
l.moveAfter(e, &l.sentinel)
}
}
// MoveToBack moves e to the back of l.
// If e is not in l, l is not modified.
func (l *List[T]) MoveToBack(e *ListElement[T]) {
l.moveAfter(e, l.sentinel.prev)
}
// MoveBefore moves e immediately before location.
// If the elements are equal or not in l, the list is not modified.
func (l *List[T]) MoveBefore(e, location *ListElement[T]) {
// Don't introduce a cycle by moving an element before itself.
if e != location {
l.moveAfter(e, location.prev)
}
}
// MoveAfter moves e immediately after location.
// If the elements are equal or not in l, the list is not modified.
func (l *List[T]) MoveAfter(e, location *ListElement[T]) {
l.moveAfter(e, location)
}
func (l *List[T]) insertAfter(e, location *ListElement[T]) {
if e.list != nil {
// Don't insert an element that is already in a list
return
}
e.prev = location
e.next = location.next
e.prev.next = e
e.next.prev = e
e.list = l
l.length++
}
func (l *List[T]) moveAfter(e, location *ListElement[T]) {
if e.list != l || location.list != l || e == location {
// Don't modify an element that is in a different list.
// Don't introduce a cycle by moving an element after itself.
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = location
e.next = location.next
e.prev.next = e
e.next.prev = e
}
// PushFront inserts v into a new element at the front of l.
func PushFront[T any](l *List[T], v T) {
l.PushFront(&ListElement[T]{
Value: v,
})
}
// PushBack inserts v into a new element at the back of l.
func PushBack[T any](l *List[T], v T) {
l.PushBack(&ListElement[T]{
Value: v,
})
}
// InsertBefore inserts v into a new element immediately before location.
// If location is not in l, l is not modified.
func InsertBefore[T any](l *List[T], v T, location *ListElement[T]) {
l.InsertBefore(
&ListElement[T]{
Value: v,
},
location,
)
}
// InsertAfter inserts v into a new element immediately after location.
// If location is not in l, l is not modified.
func InsertAfter[T any](l *List[T], v T, location *ListElement[T]) {
l.InsertAfter(
&ListElement[T]{
Value: v,
},
location,
)
}
+1 -1
View File
@@ -5,7 +5,7 @@ package math
import ( import (
"github.com/luxfi/ids" "github.com/luxfi/ids"
"github.com/luxfi/utils/heap" "github.com/luxfi/math/heap"
) )
var _ AveragerHeap = (*averagerHeap)(nil) var _ AveragerHeap = (*averagerHeap)(nil)
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
"github.com/luxfi/utils" luxmath "github.com/luxfi/math"
) )
var ( var (
@@ -36,7 +36,7 @@ func Add64(a, b uint64) (uint64, error) {
// 2) If there is underflow, an error // 2) If there is underflow, an error
func Sub[T constraints.Unsigned](a, b T) (T, error) { func Sub[T constraints.Unsigned](a, b T) (T, error) {
if a < b { if a < b {
return utils.Zero[T](), ErrUnderflow return luxmath.Zero[T](), ErrUnderflow
} }
return a - b, nil return a - b, nil
} }
+10 -21
View File
@@ -8,11 +8,8 @@ import (
"encoding/json" "encoding/json"
"slices" "slices"
luxmath "github.com/luxfi/math"
"github.com/luxfi/sampler" "github.com/luxfi/sampler"
"github.com/luxfi/utils"
"github.com/luxfi/utils/wrappers"
luxjson "github.com/luxfi/utils/json"
) )
const minSetSize = 16 const minSetSize = 16
@@ -111,7 +108,7 @@ func (s *SampleableSet[T]) Remove(elements ...T) {
func (s *SampleableSet[T]) Clear() { func (s *SampleableSet[T]) Clear() {
clear(s.indices) clear(s.indices)
for i := range s.elements { for i := range s.elements {
s.elements[i] = utils.Zero[T]() s.elements[i] = luxmath.Zero[T]()
} }
s.elements = s.elements[:0] s.elements = s.elements[:0]
} }
@@ -151,7 +148,7 @@ func (s SampleableSet[T]) Sample(numToSample int) []T {
func (s *SampleableSet[T]) UnmarshalJSON(b []byte) error { func (s *SampleableSet[T]) UnmarshalJSON(b []byte) error {
str := string(b) str := string(b)
if str == luxjson.Null { if str == "null" {
return nil return nil
} }
var elements []T var elements []T
@@ -178,24 +175,16 @@ func (s *SampleableSet[_]) MarshalJSON() ([]byte, error) {
slices.SortFunc(elementBytes, bytes.Compare) slices.SortFunc(elementBytes, bytes.Compare)
// Build the JSON // Build the JSON
var ( var jsonBuf bytes.Buffer
jsonBuf = bytes.Buffer{} jsonBuf.WriteString("[")
errs = wrappers.Errs{}
)
_, err = jsonBuf.WriteString("[")
errs.Add(err)
for i, elt := range elementBytes { for i, elt := range elementBytes {
_, err := jsonBuf.Write(elt) jsonBuf.Write(elt)
errs.Add(err)
if i != len(elementBytes)-1 { if i != len(elementBytes)-1 {
_, err := jsonBuf.WriteString(",") jsonBuf.WriteString(",")
errs.Add(err)
} }
} }
_, err = jsonBuf.WriteString("]") jsonBuf.WriteString("]")
errs.Add(err) return jsonBuf.Bytes(), nil
return jsonBuf.Bytes(), errs.Err
} }
func (s *SampleableSet[T]) resize(size int) { func (s *SampleableSet[T]) resize(size int) {
@@ -232,6 +221,6 @@ func (s *SampleableSet[T]) remove(e T) {
} }
delete(s.indices, e) delete(s.indices, e)
s.elements[lastIndex] = utils.Zero[T]() s.elements[lastIndex] = luxmath.Zero[T]()
s.elements = s.elements[:lastIndex] s.elements = s.elements[:lastIndex]
} }
+16
View File
@@ -0,0 +1,16 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package math
import "bytes"
// IsSortedBytes returns true if s is sorted in ascending order.
func IsSortedBytes[T ~[]byte](s []T) bool {
for i := 0; i < len(s)-1; i++ {
if bytes.Compare(s[i], s[i+1]) == 1 {
return false
}
}
return true
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package math
// Zero returns the zero value of any type T.
func Zero[T any]() T {
var zero T
return zero
}