// Bound-pair typed-adjacency queries: counting and position-seeking the// m-matched relationships between two given endpoints. Each side-picks the// lower-degree endpoint's run (a reverse run lists the same relationships)// across the typed-view, below-floor run, and sorted edge-key tiers. Split// from typedadj.go, which holds the view construction.
package chickpeasimport// CountNeighborsMatch counts the m-matched dir relationships from u to v// -- the bound-both-endpoints existence/multiplicity probe. Each// direction scans the lower-degree endpoint's run (v's reverse run lists// the same relationships), through the typed view when one exists;// result multiset cardinality is identical to filtering an enumeration.func ( *Snapshot) (, NodeID, Direction, RelMatch) int { := 0if == Outgoing || == Both { += .countDirMatch(, , true, ) }if == Incoming || == Both { += .countDirMatch(, , false, ) }return}// countDirMatch counts one direction's u->v matches, side-picked by run// length.func ( *Snapshot) (, NodeID, bool, RelMatch) int {if := .tp.view(); != nil { := .tp.view(!) , := relRange(.offsets, )if != nil {if , := relRange(.offsets, ); - < - {returncountHits(.nbrs[:], ) } }returncountHits(.nbrs[:], ) }// Below the typed-view floor a single type still answers a bound-pair // count in O(log E) off its sorted edge-key set: the untyped scan pays // the node's FULL degree per probe (every type's relationships), which // dominates existence-heavy predicates over small types.if .tp != nil { := .tp.edgeKeys() , := , if ! { , = , }returncountKeyHits(, uint64()<<32|uint64(uint32())) }// Scan fallback: u's primary run with per-rel type tests. , , := .outOffsets, .outNbrs, .outTypesif ! { , , = .inOffsets, .inNbrs, .inTypes } , := relRange(, ) := 0for := ; < ; ++ {if [] == && .matches([]) { ++ } }return}// edgeKeys lazily builds the type's sorted (src<<32|dst) key multiset from// one pass over the primary CSR: memory is proportional to the type's// relationship count (unlike a typed view's id-space offsets), duplicates// preserve parallel-relationship multiplicity, and one array answers both// directions (an incoming u->v probe is the key (v, u)).func ( *typedPair) () []uint64 { .edgeOnce.Do(func() { := .gvarintif , := .typeIndex[.t]; { = .Len() } := make([]uint64, 0, )for := 0; +1 < len(.outOffsets); ++ {for := .outOffsets[]; < .outOffsets[+1]; ++ {if .outTypes[] == .t { = append(, uint64(uint32())<<32|uint64(uint32(.outNbrs[]))) } } }slices.Sort() .edges = })return .edges}// countKeyHits counts key's occurrences in the sorted key multiset.func countKeyHits( []uint64, uint64) int { , := slices.BinarySearch(, ) := 0for := ; < len() && [] == ; ++ { ++ }return}// countHits counts occurrences of target in run.func countHits( []NodeID, NodeID) int { := 0for , := range {if == { ++ } }return}// AppendRelsBetweenMatch appends the stored CSR position of each m-matched// dir relationship between u and v to dst, preserving parallel-relationship// multiplicity -- the bound-both-endpoints position seek behind a named-rel// rebind expand. Like CountNeighborsMatch it scans whichever endpoint's run// is shorter (v's reverse run lists the same relationships as u's forward// run), so the cost is the smaller endpoint's degree rather than always the// from-side's full degree. Positions come out in the stored (outgoing)// frame regardless of which side was scanned, so a subsequent property read// by position is correct. Each direction's appended segment is sorted// ascending, matching the forward-scan emission order so the seek is// order-identical to the enumerate-and-filter path.func ( *Snapshot) ( []uint32, , NodeID, Direction, RelMatch) []uint32 {if == Outgoing || == Both { = .appendDirPosMatch(, , , true, ) }if == Incoming || == Both { = .appendDirPosMatch(, , , false, ) }return}// appendDirPosMatch appends one direction's u->v matched relationship// positions, side-picked by run length. out reports whether u is the source// (the forward frame); the reverse run of v lists the same relationships.// The reverse (v-side) scan is taken only when the graph has rel properties// -- the only state in which a relationship position is ever read (endpoints,// type, properties all index the outgoing frame), and thus the only state in// which the incoming->outgoing position map is consulted. With no rel// properties positions are unread and the forward scan keeps the frame// identical to the enumerate path.func ( *Snapshot) ( []uint32, , NodeID, bool, RelMatch) []uint32 { := len() := .hasRelProps// Typed-view tier: both endpoints have a contiguous per-type run; scan // the shorter one.if := .tp.view(); != nil { , := relRange(.offsets, )if := .tp.view(!); && != nil {if , := relRange(.offsets, ); - < - { = appendPosEq(, .nbrs, .poss, , , )returnsortTailU32(, ) } } = appendPosEq(, .nbrs, .poss, , , )returnsortTailU32(, ) }// Below-floor tier: single type with payload-proportional run views; the // run poss carry the stored frame the same way. Side-pick by run length.if .tp != nil {if := .tp.runs(); != nil { , := .runRange()if := .tp.runs(!); && != nil {if , := .runRange(); - < - { = appendPosEq(, .nbrs, .poss, , , )returnsortTailU32(, ) } } = appendPosEq(, .nbrs, .poss, , , )returnsortTailU32(, ) } }// Scan fallback (multi-type / match-all matcher): u's primary run with // per-rel type tests, positions mapped to the stored frame for incoming. , , , := .outOffsets, .outNbrs, .outTypes, []uint32(nil)if ! { , , , = .inOffsets, .inNbrs, .inTypes, .getInToOut() } , := relRange(, )for := ; < ; ++ {if [] == && .matches([]) { := uint32()if < len() { = [] } = append(, ) } }return}// appendPosEq appends poss[k] for each k in [lo, hi) whose neighbor equals// target -- the matching relationships of a bound pair within one scanned// run.func appendPosEq( []uint32, []NodeID, []uint32, , int, NodeID) []uint32 {for := ; < ; ++ {if [] == { = append(, []) } }return}// sortTailU32 sorts dst[base:] ascending in place (a no-op for the common// single-relationship pair), so a side-picked reverse scan yields the same// order as the forward scan.func sortTailU32( []uint32, int) []uint32 {iflen()- > 1 {slices.Sort([:]) }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.