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