Files
math/set/sampleable_set.go
Zach Kelling 107c43637e 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
2025-12-24 15:06:41 -08:00

227 lines
5.0 KiB
Go

// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package set
import (
"bytes"
"encoding/json"
"slices"
luxmath "github.com/luxfi/math"
"github.com/luxfi/sampler"
)
const minSetSize = 16
var _ json.Marshaler = (*SampleableSet[int])(nil)
// SampleableSet is a set of elements that supports sampling.
type SampleableSet[T comparable] struct {
// indices maps the element in the set to the index that it appears in
// elements.
indices map[T]int
elements []T
}
// OfSampleable returns a Set initialized with [elts]
func OfSampleable[T comparable](elts ...T) SampleableSet[T] {
s := NewSampleableSet[T](len(elts))
s.Add(elts...)
return s
}
// Return a new sampleable set with initial capacity [size].
// More or less than [size] elements can be added to this set.
// Using NewSampleableSet() rather than SampleableSet[T]{} is just an
// optimization that can be used if you know how many elements will be put in
// this set.
func NewSampleableSet[T comparable](size int) SampleableSet[T] {
if size < 0 {
return SampleableSet[T]{}
}
return SampleableSet[T]{
indices: make(map[T]int, size),
elements: make([]T, 0, size),
}
}
// Add all the elements to this set.
// If the element is already in the set, nothing happens.
func (s *SampleableSet[T]) Add(elements ...T) {
s.resize(2 * len(elements))
for _, e := range elements {
s.add(e)
}
}
// Union adds all the elements from the provided set to this set.
func (s *SampleableSet[T]) Union(set SampleableSet[T]) {
s.resize(2 * set.Len())
for _, e := range set.elements {
s.add(e)
}
}
// Difference removes all the elements in [set] from [s].
func (s *SampleableSet[T]) Difference(set SampleableSet[T]) {
for _, e := range set.elements {
s.remove(e)
}
}
// Contains returns true iff the set contains this element.
func (s SampleableSet[T]) Contains(e T) bool {
_, contains := s.indices[e]
return contains
}
// Overlaps returns true if the intersection of the set is non-empty
func (s SampleableSet[T]) Overlaps(big SampleableSet[T]) bool {
small := s
if small.Len() > big.Len() {
small, big = big, small
}
for _, e := range small.elements {
if _, ok := big.indices[e]; ok {
return true
}
}
return false
}
// Len returns the number of elements in this set.
func (s SampleableSet[_]) Len() int {
return len(s.elements)
}
// Remove all the given elements from this set.
// If an element isn't in the set, it's ignored.
func (s *SampleableSet[T]) Remove(elements ...T) {
for _, e := range elements {
s.remove(e)
}
}
// Clear empties this set
func (s *SampleableSet[T]) Clear() {
clear(s.indices)
for i := range s.elements {
s.elements[i] = luxmath.Zero[T]()
}
s.elements = s.elements[:0]
}
// List converts this set into a list
func (s SampleableSet[T]) List() []T {
return slices.Clone(s.elements)
}
// Equals returns true if the sets contain the same elements
func (s SampleableSet[T]) Equals(other SampleableSet[T]) bool {
if len(s.indices) != len(other.indices) {
return false
}
for k := range s.indices {
if _, ok := other.indices[k]; !ok {
return false
}
}
return true
}
func (s SampleableSet[T]) Sample(numToSample int) []T {
if numToSample <= 0 {
return nil
}
uniform := sampler.NewUniform()
uniform.Initialize(uint64(len(s.elements)))
indices, _ := uniform.Sample(min(len(s.elements), numToSample))
elements := make([]T, len(indices))
for i, index := range indices {
elements[i] = s.elements[index]
}
return elements
}
func (s *SampleableSet[T]) UnmarshalJSON(b []byte) error {
str := string(b)
if str == "null" {
return nil
}
var elements []T
if err := json.Unmarshal(b, &elements); err != nil {
return err
}
s.Clear()
s.Add(elements...)
return nil
}
func (s *SampleableSet[_]) MarshalJSON() ([]byte, error) {
var (
elementBytes = make([][]byte, len(s.elements))
err error
)
for i, e := range s.elements {
elementBytes[i], err = json.Marshal(e)
if err != nil {
return nil, err
}
}
// Sort for determinism
slices.SortFunc(elementBytes, bytes.Compare)
// Build the JSON
var jsonBuf bytes.Buffer
jsonBuf.WriteString("[")
for i, elt := range elementBytes {
jsonBuf.Write(elt)
if i != len(elementBytes)-1 {
jsonBuf.WriteString(",")
}
}
jsonBuf.WriteString("]")
return jsonBuf.Bytes(), nil
}
func (s *SampleableSet[T]) resize(size int) {
if s.elements == nil {
if minSetSize > size {
size = minSetSize
}
s.indices = make(map[T]int, size)
}
}
func (s *SampleableSet[T]) add(e T) {
_, ok := s.indices[e]
if ok {
return
}
s.indices[e] = len(s.elements)
s.elements = append(s.elements, e)
}
func (s *SampleableSet[T]) remove(e T) {
indexToRemove, ok := s.indices[e]
if !ok {
return
}
lastIndex := len(s.elements) - 1
if indexToRemove != lastIndex {
lastElement := s.elements[lastIndex]
s.indices[lastElement] = indexToRemove
s.elements[indexToRemove] = lastElement
}
delete(s.indices, e)
s.elements[lastIndex] = luxmath.Zero[T]()
s.elements = s.elements[:lastIndex]
}