// Unweighted traversal search: reachability, (bidirectional) BFS over node// sets with nil-able node/rel filters, hop distances, and bounded// neighborhoods. The distance kernels reuse generation-stamped dense// scratch from a pool, so repeated searches on a large graph avoid an O(n)// clear (and allocation) per call.package chickpeasimport ()// NoMaxDepth removes the depth bound of a search.constNoMaxDepth = -1// NodeFilter gates a node's participation in a search; nil means no filter.typeNodeFilterfunc(node NodeID, g *Snapshot) bool// RelFilter gates following a rel, receiving its stored (source, target)// endpoints, type, and CSR position; nil means no filter.typeRelFilterfunc(from, to NodeID, t RelType, pos uint32, g *Snapshot) bool// CanReach reports whether to is reachable from from over the m-matched// rels in dir, within maxDepth hops (NoMaxDepth = unbounded).func ( *Snapshot) (, NodeID, Direction, RelMatch, int) bool {if == {returntrue } := nodeset.Of() := [][2]uint32{{, 0}}forlen() > 0 { , := [0][0], [0][1] = [1:]if >= 0 && int() >= {continue }for := range .NeighborsMatch(, , ) {if == {returntrue }if .Insert() { = append(, [2]uint32{, + 1}) } } }returnfalse}// bfsStep expands one node of a BFS frontier, shared by BFS and both passes// of BidirectionalBFS. backward flips the (from, to) the rel filter sees.func ( *Snapshot) ( NodeID, uint32, Direction, RelMatch,NodeFilter, RelFilter, bool, , , *nodeset.Set, *[][2]uint32) {for := range .RelsMatch(, , ) {// The rel filter sees traversal orientation -- (current, neighbor) // on the forward pass, flipped on the backward pass -- matching the // Rust kernel. , := , .Neighborif { , = .Neighbor, }if != nil && !(, , .Type, .Pos, ) {continue }// The node filter applies before recording/marking, so a rejected // node never participates regardless of which frontier reaches it.if != nil && !(.Neighbor, ) {continue }if != nil && .Contains(.Neighbor) {// Meeting point: always record the connecting rel; enqueue only // if newly visited. .Insert(.Pos)if .Insert(.Neighbor) { * = append(*, [2]uint32{.Neighbor, + 1}) }continue }if != nil {if .Insert(.Neighbor) { .Insert(.Pos) * = append(*, [2]uint32{.Neighbor, + 1}) }continue } .Insert(.Pos)if .Insert(.Neighbor) { * = append(*, [2]uint32{.Neighbor, + 1}) } }}// BFS traverses from the start set over the m-matched rels in dir,// returning every node visited and every rel CSR position traversed.// Filters gate participation (nil = none); maxDepth bounds the hop count// (NoMaxDepth = unbounded).func ( *Snapshot) ( *nodeset.Set, Direction, RelMatch,NodeFilter, RelFilter, int) (, *nodeset.Set) { , = nodeset.New(), nodeset.New()var [][2]uint32for := range .Iter() {if ( == nil || (, )) && .Insert() { = append(, [2]uint32{, 0}) } }forlen() > 0 { , := [0][0], [0][1] = [1:]if >= 0 && int() >= {continue } .bfsStep(, , , , , , false, , , nil, &) }return , }// BidirectionalBFS searches from the source and target sets// simultaneously, meeting in the middle: the forward pass follows dir, the// backward pass its reverse. Returns the nodes reached by BOTH sides (the// meeting set) and the union of rel positions either side traversed; both// empty when the sets don't connect. An immediate source/target overlap// returns it with no rels.func ( *Snapshot) (, *nodeset.Set, Direction, RelMatch,NodeFilter, RelFilter, int) (, *nodeset.Set) { , := nodeset.New(), nodeset.New() , := nodeset.New(), nodeset.New()var , [][2]uint32for := range .Iter() {if == nil || (, ) { .Insert() = append(, [2]uint32{, 0}) } }for := range .Iter() {if == nil || (, ) { .Insert() = append(, [2]uint32{, 0}) } }if := .And(); !.IsEmpty() {return , nodeset.New() } := .Reverse() := func( *[][2]uint32, , , *nodeset.Set, Direction, bool) {forrangelen(*) { // one level , := (*)[0][0], (*)[0][1] * = (*)[1:]if >= 0 && int() >= {continue } .bfsStep(, , , , , , , , , , ) } }forlen() > 0 || len() > 0 { (&, , , , , false) (&, , , , , true) } := .And()if .IsEmpty() {returnnodeset.New(), nodeset.New() }// The rel union covers everything both sides visited, not only rels // between meeting nodes (filtering those would need path // reconstruction) -- same contract as the Rust kernel.return , .Or()}// searchScratch is the generation-stamped dense visited/distance scratch// behind BFSDistances and Neighborhood: a node counts as visited this run// iff gen[node] == cur, avoiding an O(n) clear per call. Pooled on the// snapshot so concurrent searches each take their own.type searchScratch struct { gen []uint32 dist []uint32 cur uint32}var scratchPool = sync.Pool{New: func() any { return &searchScratch{} }}func takeScratch( int) *searchScratch { := scratchPool.Get().(*searchScratch)iflen(.gen) < { .gen = make([]uint32, ) .dist = make([]uint32, ) .cur = 0 } .cur++if .cur == 0 { // wrapped: reset the stamps onceclear(.gen) .cur = 1 }return}// BFSDistances is unweighted single-source BFS returning the hop distance// from start to every reached node (start itself at 0), bounded by// maxDepth (NoMaxDepth = the whole component). Cheaper than Dijkstra with// a unit weight when only hop counts are needed.func ( *Snapshot) ( NodeID, Direction, RelMatch, int) map[NodeID]uint32 { := int(.CSRIDSpace())// A start outside the CSR id space is not a node in this graph.ifint() >= {returnmap[NodeID]uint32{} } := takeScratch()deferscratchPool.Put() .gen[] = .cur .dist[] = 0 := []NodeID{} := []NodeID{} := uint32(0)forlen() > 0 {if >= 0 && int() >= {break } ++var []NodeIDfor , := range {for := range .NeighborsMatch(, , ) {if .gen[] != .cur { .gen[] = .cur .dist[] = = append(, ) = append(, ) } } } = } := make(map[NodeID]uint32, len())for , := range { [] = .dist[] }return}// Neighborhood is the set of nodes whose hop distance from seed lies in// the closed range [loHops, hiHops] -- 1..2 is "one or two hops out"// (excludes seed), 0..2 includes it, 2..2 is exactly two hops. Returned as// a set so membership is O(1) and intersecting with another set is one// bitmap op.func ( *Snapshot) ( NodeID, Direction, RelMatch, , uint32) *nodeset.Set { := nodeset.New() := int(.CSRIDSpace())if > || int() >= {return } := takeScratch()deferscratchPool.Put() .gen[] = .curif == 0 { .Insert() } := []NodeID{}for := uint32(1); <= && len() > 0; ++ {var []NodeIDfor , := range {for := range .NeighborsMatch(, , ) {if .gen[] != .cur { .gen[] = .cur = append(, )if >= { .Insert() } } } } = }return}
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.