feat(index): generic SortedIndex (slice + binary search)
This commit is contained in:
parent
98ec0bba3d
commit
1728780aed
1 changed files with 82 additions and 0 deletions
82
sorted_index.go
Normal file
82
sorted_index.go
Normal 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 }
|
||||||
Loading…
Add table
Add a link
Reference in a new issue