diff --git a/sorted_index.go b/sorted_index.go new file mode 100644 index 0000000..bbc3ccb --- /dev/null +++ b/sorted_index.go @@ -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 }