// All-shortest-paths enumeration: a reusable single-source minimum-hop// parent tree (spTree) for hop-minimal single paths, plus the forward-BFS// + backward-DFS that enumerates every distinct minimum-hop path a..b and// resolves each path's relationship positions. Split from shortest.go,// which holds the stage runner, scratch, and single-path search.
package execimport ()// spTree is a single-source minimum-hop parent tree, reused across every// row sharing that source.type spTree struct { source graph.NodeID parent map[graph.NodeID]graph.NodeID}// buildSPTree runs one bounded BFS from a, honoring the same filters the// per-row search uses so reachability and distances are identical. The// parent map is retained in the stage memo, so it allocates fresh.func buildSPTree( *eval.Ctx, graph.NodeID, *plan.SpStage, *graph.RelMatcher, *hopFilter, *spScratch) *spTree { := map[graph.NodeID]graph.NodeID{}spWalk(, , , , , , func(, graph.NodeID, uint64) bool { [] = returnfalse }, nil)return &spTree{source: , parent: }}// pathTo reads the minimum-hop path source..b off the parent links,// returned by value.func ( *spTree) ( *eval.Ctx, graph.NodeID, *plan.SpStage, *graph.RelMatcher, *hopFilter, *spScratch) (nodesRels, bool) { := pathFromParents(, .parent, .source, )if == nil {returnnodesRels{}, false }returnnodesRels{nodes: , rels: pathRelPositions(, , , .Dir, , )}, true}// allShortestPaths enumerates every minimum-hop path a..b: a forward BFS// records each reached node's minimum distance, then a backward DFS from b// walks predecessor chains strictly descending the distance -- each// completed chain is a distinct minimum-hop path.func allShortestPaths( *eval.Ctx, , graph.NodeID, *plan.SpStage, *graph.RelMatcher, *hopFilter, *spScratch) []nodesRels {if == { := .nodeSlice(1) [0] = return []nodesRels{{nodes: }} }// spWalk opens the generation; dist entries are valid under its stamp // (scr.cur) until the next search begins.spWalk(, , , , , , func(, graph.NodeID, uint64) bool { .dist[] = uint32()returnfalse }, func() bool {return .gen[] == .cur })if .gen[] != .cur {returnnil } := flipDir(.Dir)var [][]graph.NodeID := []graph.NodeID{}if .preds == nil { .preds = map[graph.NodeID][]graph.NodeID{} } else {clear(.preds) }enumeratePaths(, , , , , , &, &) := make([]nodesRels, len())for , := range { [] = nodesRels{nodes: , rels: pathRelPositions(, , , .Dir, , )} }return}// enumeratePaths extends the reversed suffix [b..v] by each predecessor u// whose stamped distance is dist[v]-1; reaching a completes one path. The// enumeration is COMPLETE by decision -- a min-hop DAG can fan out// combinatorially and every path is the operator's contract -- so the// work is kept output-proportional: each node's predecessor sequence is// resolved once per enumeration (predsOf) instead of re-scanning its// adjacency on every path through it, which charged a hub's whole degree// per path.func enumeratePaths( *eval.Ctx, graph.NodeID, *spScratch, graph.Direction, *graph.RelMatcher, *hopFilter, *[]graph.NodeID, *[][]graph.NodeID) { := (*)[len(*)-1]if == { := .nodeSlice(len(*))copy(, *)reverseNodes() * = append(*, )return }for , := rangepredsOf(, , , , , ) { * = append(*, ) (, , , , , , , ) * = (*)[:len(*)-1] }}// predsOf memoizes v's minimum-hop predecessor sequence for the current// enumeration, preserving the exact order (and any multigraph duplicates)// the filtered neighbor walk yields -- the memo must be sequence-// identical to the walk it replaces, not merely set-equal, so path output// order is unchanged.func predsOf( *eval.Ctx, graph.NodeID, *spScratch, graph.Direction, *graph.RelMatcher, *hopFilter) []graph.NodeID {if , := .preds[]; {return } := .dist[] - 1 := []graph.NodeID{}filteredNeighbors(, , , , , func( graph.NodeID) {if .gen[] == .cur && .dist[] == { = append(, ) } }) .preds[] = return}// pathRelPositions resolves each consecutive node pair's relationship// position (the first accepted relationship between them). Each hop reads// from its lower-degree endpoint -- resolving from the higher-degree side// pays a hub's whole adjacency, and a path found by never expanding a// hub's ply must not re-pay the hub here. The read goes through the batch// seam into the walk scratch's neighbor buffers (an iterator closure per// hop dominated shortest-path stages' allocations); it runs only after a// walk completes, so the buffers are free (same rule as// appendHopNeighbors). Positions read identically from either side (the// incoming seam maps to stored positions), so the side pick cannot change// the resolved rel set.func pathRelPositions( *eval.Ctx, *spScratch, []graph.NodeID, graph.Direction, *graph.RelMatcher, *hopFilter) []uint32 { := .relSlice(max(len()-1, 0))[:0] := flipDir()for := 0; +1 < len(); ++ { , := [], [+1] , , := , , if .G.Degree(, ) < .G.Degree(, ) { , , = , , } .nbNodes, .nbPoss = .G.AppendRelationshipsMatched(.nbNodes[:0], .nbPoss[:0], , , )for , := range .nbNodes {if == && ( == nil || .keep(, .nbPoss[])) { = append(, .nbPoss[])break } } }return}// reverseNodes reverses a node sequence in place.func reverseNodes( []graph.NodeID) {for , := 0, len()-1; < ; , = +1, -1 { [], [] = [], [] }}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.