From 107c43637ebab4e576f5e1a68cce5d943c311c53 Mon Sep 17 00:00:00 2001 From: Zach Kelling Date: Wed, 24 Dec 2025 15:06:41 -0800 Subject: [PATCH] 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 --- go.mod | 2 - go.sum | 4 - heap/map.go | 132 +++++++++++++++++++++++++ heap/queue.go | 94 ++++++++++++++++++ linked/hashmap.go | 166 ++++++++++++++++++++++++++++++++ linked/list.go | 217 ++++++++++++++++++++++++++++++++++++++++++ math/averager_heap.go | 2 +- math/safe_math.go | 4 +- set/sampleable_set.go | 31 ++---- sorting.go | 16 ++++ zero.go | 10 ++ 11 files changed, 648 insertions(+), 30 deletions(-) create mode 100644 heap/map.go create mode 100644 heap/queue.go create mode 100644 linked/hashmap.go create mode 100644 linked/list.go create mode 100644 sorting.go create mode 100644 zero.go diff --git a/go.mod b/go.mod index 4c4cd4a..30203f0 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,12 @@ require ( github.com/luxfi/geth v1.16.52 github.com/luxfi/ids v1.2.4 github.com/luxfi/sampler v1.0.0 - github.com/luxfi/utils v1.0.0 github.com/stretchr/testify v1.11.1 golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b ) require ( 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/mr-tron/base58 v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/go.sum b/go.sum index c9f2e4d..e0d8d9a 100644 --- a/go.sum +++ b/go.sum @@ -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/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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/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/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/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= diff --git a/heap/map.go b/heap/map.go new file mode 100644 index 0000000..43281e1 --- /dev/null +++ b/heap/map.go @@ -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 +} diff --git a/heap/queue.go b/heap/queue.go new file mode 100644 index 0000000..0a54a3f --- /dev/null +++ b/heap/queue.go @@ -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 +} diff --git a/linked/hashmap.go b/linked/hashmap.go new file mode 100644 index 0000000..94d3f5e --- /dev/null +++ b/linked/hashmap.go @@ -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 +} diff --git a/linked/list.go b/linked/list.go new file mode 100644 index 0000000..436181c --- /dev/null +++ b/linked/list.go @@ -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, + ) +} diff --git a/math/averager_heap.go b/math/averager_heap.go index 5424902..0643143 100644 --- a/math/averager_heap.go +++ b/math/averager_heap.go @@ -5,7 +5,7 @@ package math import ( "github.com/luxfi/ids" - "github.com/luxfi/utils/heap" + "github.com/luxfi/math/heap" ) var _ AveragerHeap = (*averagerHeap)(nil) diff --git a/math/safe_math.go b/math/safe_math.go index 679d983..51e2104 100644 --- a/math/safe_math.go +++ b/math/safe_math.go @@ -9,7 +9,7 @@ import ( "golang.org/x/exp/constraints" - "github.com/luxfi/utils" + luxmath "github.com/luxfi/math" ) var ( @@ -36,7 +36,7 @@ func Add64(a, b uint64) (uint64, error) { // 2) If there is underflow, an error func Sub[T constraints.Unsigned](a, b T) (T, error) { if a < b { - return utils.Zero[T](), ErrUnderflow + return luxmath.Zero[T](), ErrUnderflow } return a - b, nil } diff --git a/set/sampleable_set.go b/set/sampleable_set.go index b79e002..7266b03 100644 --- a/set/sampleable_set.go +++ b/set/sampleable_set.go @@ -8,11 +8,8 @@ import ( "encoding/json" "slices" + luxmath "github.com/luxfi/math" "github.com/luxfi/sampler" - "github.com/luxfi/utils" - "github.com/luxfi/utils/wrappers" - - luxjson "github.com/luxfi/utils/json" ) const minSetSize = 16 @@ -111,7 +108,7 @@ func (s *SampleableSet[T]) Remove(elements ...T) { func (s *SampleableSet[T]) Clear() { clear(s.indices) for i := range s.elements { - s.elements[i] = utils.Zero[T]() + s.elements[i] = luxmath.Zero[T]() } 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 { str := string(b) - if str == luxjson.Null { + if str == "null" { return nil } var elements []T @@ -178,24 +175,16 @@ func (s *SampleableSet[_]) MarshalJSON() ([]byte, error) { slices.SortFunc(elementBytes, bytes.Compare) // Build the JSON - var ( - jsonBuf = bytes.Buffer{} - errs = wrappers.Errs{} - ) - _, err = jsonBuf.WriteString("[") - errs.Add(err) + var jsonBuf bytes.Buffer + jsonBuf.WriteString("[") for i, elt := range elementBytes { - _, err := jsonBuf.Write(elt) - errs.Add(err) + jsonBuf.Write(elt) if i != len(elementBytes)-1 { - _, err := jsonBuf.WriteString(",") - errs.Add(err) + jsonBuf.WriteString(",") } } - _, err = jsonBuf.WriteString("]") - errs.Add(err) - - return jsonBuf.Bytes(), errs.Err + jsonBuf.WriteString("]") + return jsonBuf.Bytes(), nil } func (s *SampleableSet[T]) resize(size int) { @@ -232,6 +221,6 @@ func (s *SampleableSet[T]) remove(e T) { } delete(s.indices, e) - s.elements[lastIndex] = utils.Zero[T]() + s.elements[lastIndex] = luxmath.Zero[T]() s.elements = s.elements[:lastIndex] } diff --git a/sorting.go b/sorting.go new file mode 100644 index 0000000..44b31b2 --- /dev/null +++ b/sorting.go @@ -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 +} diff --git a/zero.go b/zero.go new file mode 100644 index 0000000..9b79b97 --- /dev/null +++ b/zero.go @@ -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 +}