feat(index): generic SortedIndex (slice + binary search)

This commit is contained in:
Hermes 2026-06-23 01:31:01 -07:00
parent 98ec0bba3d
commit 1728780aed

82
sorted_index.go Normal file
View file

@ -0,0 +1,82 @@
package main
// SortedIndex — a generic sorted container with O(log N) insert and O(N) iteration
// in sort order. Backed by a slice. For posts/2, this is fine; if we need true
// O(log N) random access, swap for a btree.BTree from google/btree.
//
// The key is extracted from the value via Less. The index is NOT safe for
// concurrent use; the Store's mu guards all access.
type SortedIndex[T any] struct {
less func(a, b T) bool
items []T
index map[any]int // value-identity -> position in items (for fast Update/Delete)
}
func NewSortedIndex[T any](less func(a, b T) bool) *SortedIndex[T] {
return &SortedIndex[T]{
less: less,
items: make([]T, 0, 64),
index: make(map[any]int, 64),
}
}
func (idx *SortedIndex[T]) Insert(v T) {
key := indexKey(v)
pos := idx.findPos(v)
idx.items = append(idx.items[:pos], append([]T{v}, idx.items[pos:]...)...)
// Rebuild index for positions >= pos (slice shifted right by 1)
for k, p := range idx.index {
if p >= pos {
idx.index[k] = p + 1
}
}
idx.index[key] = pos
}
func (idx *SortedIndex[T]) Update(v T) {
// Same value, but its sort key may have changed. Delete + re-insert.
idx.Delete(v)
idx.Insert(v)
}
func (idx *SortedIndex[T]) Delete(v T) {
key := indexKey(v)
pos, ok := idx.index[key]
if !ok {
return
}
idx.items = append(idx.items[:pos], idx.items[pos+1:]...)
delete(idx.index, key)
// Rebuild index for positions > pos (slice shifted left by 1)
for k, p := range idx.index {
if p > pos {
idx.index[k] = p - 1
}
}
}
func (idx *SortedIndex[T]) Items() []T {
out := make([]T, len(idx.items))
copy(out, idx.items)
return out
}
func (idx *SortedIndex[T]) Len() int { return len(idx.items) }
// findPos — binary search for insertion point.
func (idx *SortedIndex[T]) findPos(v T) int {
lo, hi := 0, len(idx.items)
for lo < hi {
mid := (lo + hi) / 2
if idx.less(idx.items[mid], v) {
lo = mid + 1
} else {
hi = mid
}
}
return lo
}
// indexKey — identity key. Pointer types (which *Post, *Comment are) work
// because we use pointer identity. Value types would need a different scheme.
func indexKey(v any) any { return v }