Add private canonical path prefix index
This commit is contained in:
@@ -177,7 +177,7 @@ document comparison, and diagnostics.
|
||||
Validate: `go test ./internal/engine -run 'Resolution|Override|Overlay|Diagnostic|CompareDocument|Provenance'`
|
||||
and the everyday check. Expected benefit is reduced formatting, not measured CPU.
|
||||
|
||||
## Stage 6: Add a private canonical-pointer prefix index
|
||||
## Stage 6: Add a private canonical-pointer prefix index — Complete
|
||||
|
||||
Scope: one helper plus tests, no caller migration yet.
|
||||
|
||||
|
||||
138
internal/engine/path_prefix.go
Normal file
138
internal/engine/path_prefix.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package engine
|
||||
|
||||
type pathPrefixIndex struct {
|
||||
root pathPrefixNode
|
||||
}
|
||||
|
||||
type pathPrefixNode struct {
|
||||
children map[string]*pathPrefixNode
|
||||
ids []int
|
||||
minimumID int
|
||||
hasMinimum bool
|
||||
}
|
||||
|
||||
func (node *pathPrefixNode) includeID(id int) {
|
||||
if !node.hasMinimum || id < node.minimumID {
|
||||
node.minimumID = id
|
||||
node.hasMinimum = true
|
||||
}
|
||||
}
|
||||
|
||||
func (index *pathPrefixIndex) insert(pointer string, id int) {
|
||||
node := &index.root
|
||||
node.includeID(id)
|
||||
visitCanonicalPointerTokens(pointer, func(token string) bool {
|
||||
if node.children == nil {
|
||||
node.children = make(map[string]*pathPrefixNode)
|
||||
}
|
||||
child := node.children[token]
|
||||
if child == nil {
|
||||
child = &pathPrefixNode{}
|
||||
node.children[token] = child
|
||||
}
|
||||
node = child
|
||||
node.includeID(id)
|
||||
return true
|
||||
})
|
||||
node.ids = append(node.ids, id)
|
||||
}
|
||||
|
||||
func (index *pathPrefixIndex) visitAncestors(pointer string, visit func(int)) {
|
||||
node := &index.root
|
||||
for _, id := range node.ids {
|
||||
visit(id)
|
||||
}
|
||||
visitCanonicalPointerTokens(pointer, func(token string) bool {
|
||||
child := node.children[token]
|
||||
if child == nil {
|
||||
return false
|
||||
}
|
||||
node = child
|
||||
for _, id := range node.ids {
|
||||
visit(id)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (index *pathPrefixIndex) visitDescendants(pointer string, visit func(int)) {
|
||||
root := index.nodeAt(pointer)
|
||||
if root == nil {
|
||||
return
|
||||
}
|
||||
var walk func(*pathPrefixNode)
|
||||
walk = func(node *pathPrefixNode) {
|
||||
for _, id := range node.ids {
|
||||
visit(id)
|
||||
}
|
||||
for _, child := range node.children {
|
||||
walk(child)
|
||||
}
|
||||
}
|
||||
walk(root)
|
||||
}
|
||||
|
||||
func (index *pathPrefixIndex) earliestIntersectingID(pointer string) (int, bool) {
|
||||
bestID, found := 0, false
|
||||
consider := func(id int) {
|
||||
if !found || id < bestID {
|
||||
bestID = id
|
||||
found = true
|
||||
}
|
||||
}
|
||||
node := &index.root
|
||||
for _, id := range node.ids {
|
||||
consider(id)
|
||||
}
|
||||
complete := visitCanonicalPointerTokens(pointer, func(token string) bool {
|
||||
child := node.children[token]
|
||||
if child == nil {
|
||||
return false
|
||||
}
|
||||
node = child
|
||||
for _, id := range node.ids {
|
||||
consider(id)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if complete && node.hasMinimum {
|
||||
consider(node.minimumID)
|
||||
}
|
||||
return bestID, found
|
||||
}
|
||||
|
||||
func (index *pathPrefixIndex) nodeAt(pointer string) *pathPrefixNode {
|
||||
node := &index.root
|
||||
complete := visitCanonicalPointerTokens(pointer, func(token string) bool {
|
||||
child := node.children[token]
|
||||
if child == nil {
|
||||
return false
|
||||
}
|
||||
node = child
|
||||
return true
|
||||
})
|
||||
if !complete {
|
||||
return nil
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func visitCanonicalPointerTokens(pointer string, visit func(string) bool) bool {
|
||||
if pointer == "" {
|
||||
return true
|
||||
}
|
||||
start := 1
|
||||
for {
|
||||
end := start
|
||||
for end < len(pointer) && pointer[end] != '/' {
|
||||
end++
|
||||
}
|
||||
if !visit(pointer[start:end]) {
|
||||
return false
|
||||
}
|
||||
if end == len(pointer) {
|
||||
return true
|
||||
}
|
||||
start = end + 1
|
||||
}
|
||||
}
|
||||
81
internal/engine/path_prefix_test.go
Normal file
81
internal/engine/path_prefix_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPathPrefixIndexMatchesPointerScans(t *testing.T) {
|
||||
entries := []struct {
|
||||
pointer string
|
||||
id int
|
||||
}{
|
||||
{pointer: "", id: 12},
|
||||
{pointer: "/", id: 9},
|
||||
{pointer: "/a", id: 6},
|
||||
{pointer: "/a", id: 2},
|
||||
{pointer: "/a/b", id: 4},
|
||||
{pointer: "/a/b/c", id: 0},
|
||||
{pointer: "/a/bc", id: 10},
|
||||
{pointer: "/a~1b", id: 3},
|
||||
{pointer: "/a~0b", id: 1},
|
||||
{pointer: "/a~01b", id: 5},
|
||||
{pointer: "/items/0", id: 8},
|
||||
{pointer: "/items/01", id: 7},
|
||||
{pointer: "/items/+1", id: 11},
|
||||
}
|
||||
var index pathPrefixIndex
|
||||
for _, entry := range entries {
|
||||
index.insert(entry.pointer, entry.id)
|
||||
}
|
||||
queries := []string{
|
||||
"", "/", "/a", "/a/b", "/a/b/c", "/a/b/c/d", "/a/bc", "/a/bc/d",
|
||||
"/a~1b", "/a~0b", "/a~01b", "/items", "/items/0", "/items/01", "/items/1", "/missing",
|
||||
}
|
||||
for _, query := range queries {
|
||||
gotAncestors := make([]int, 0)
|
||||
index.visitAncestors(query, func(id int) { gotAncestors = append(gotAncestors, id) })
|
||||
wantAncestors := make([]int, 0)
|
||||
gotDescendants := make([]int, 0)
|
||||
wantDescendants := make([]int, 0)
|
||||
wantEarliest, foundEarliest := 0, false
|
||||
for _, entry := range entries {
|
||||
if pointerContainsByString(entry.pointer, query) {
|
||||
wantAncestors = append(wantAncestors, entry.id)
|
||||
}
|
||||
if pointerContainsByString(query, entry.pointer) {
|
||||
wantDescendants = append(wantDescendants, entry.id)
|
||||
}
|
||||
if pointerContainsByString(entry.pointer, query) || pointerContainsByString(query, entry.pointer) {
|
||||
if !foundEarliest || entry.id < wantEarliest {
|
||||
wantEarliest, foundEarliest = entry.id, true
|
||||
}
|
||||
}
|
||||
}
|
||||
index.visitDescendants(query, func(id int) { gotDescendants = append(gotDescendants, id) })
|
||||
if !slices.Equal(sortedPrefixIDs(gotAncestors), sortedPrefixIDs(wantAncestors)) {
|
||||
t.Errorf("ancestors(%q) = %v, want %v", query, sortedPrefixIDs(gotAncestors), sortedPrefixIDs(wantAncestors))
|
||||
}
|
||||
if !slices.Equal(sortedPrefixIDs(gotDescendants), sortedPrefixIDs(wantDescendants)) {
|
||||
t.Errorf("descendants(%q) = %v, want %v", query, sortedPrefixIDs(gotDescendants), sortedPrefixIDs(wantDescendants))
|
||||
}
|
||||
gotEarliest, gotFound := index.earliestIntersectingID(query)
|
||||
if gotEarliest != wantEarliest || gotFound != foundEarliest {
|
||||
t.Errorf("earliestIntersectingID(%q) = %d, %v, want %d, %v", query, gotEarliest, gotFound, wantEarliest, foundEarliest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pointerContainsByString(container, descendant string) bool {
|
||||
if container == "" {
|
||||
return true
|
||||
}
|
||||
return descendant == container || strings.HasPrefix(descendant, container+"/")
|
||||
}
|
||||
|
||||
func sortedPrefixIDs(ids []int) []int {
|
||||
result := slices.Clone(ids)
|
||||
slices.Sort(result)
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user