// NeighborGroups: a lazy query over each source node's typed neighbors,// grouped by a projected attribute and reduced per source. Nothing runs// until a terminal (Sizes / TopBySize) is called; reductions run in// parallel over the sources with projection and counting kept native.package chickpeasimport ()// NeighborGroups is built by Snapshot.NeighborGroups; chain Project, then// call a terminal.typeNeighborGroupsstruct { g *Snapshot sources []NodeID m RelMatch dir Direction project []Step}// SourceSize pairs a source node with its largest-cohort size.typeSourceSizestruct { Source NodeID Size uint32}// NeighborGroups starts a grouped-neighbor reduction over each source's// m-matched neighbors in dir.func ( *Snapshot) ( []NodeID, RelMatch, Direction) *NeighborGroups {return &NeighborGroups{g: , sources: , m: , dir: }}// Project maps each neighbor to its group node via a chain of// first-neighbor steps (like Follow). Without a projection, neighbors// group by their own id (every cohort is 1).func ( *NeighborGroups) ( ...Step) *NeighborGroups { .project = return}// Sizes is the raw reduction: per source, the size of its largest cohort.// Sources with no projectable neighbors yield 0; an unknown rel type or// projection type yields all-zero sizes. Runs in parallel over the// sources; output order follows the input sources.func ( *NeighborGroups) () []SourceSize { := make([]SourceSize, len(.sources))// Resolve the projection chain once; an unknown type zeroes everything. := make([]struct {DirectionRelMatch }, len(.project)) := truefor , := range .project { , := .g.RelType(.RelType)if ! { = falsebreak } []. = .Dir []. = MatchType() }if ! {for , := range .sources { [] = SourceSize{Source: } }return }// One contiguous range per WORKER (not per 4x-oversplit chunk): the // per-range scratch is heavy -- a counts map and a neighbors buffer -- // and both reach a high-water bounded by a single source (counts // clears per source, the buffer by max degree), so fewer ranges cost // strictly fewer allocations with no growth penalty. Neighbors batch // into the reused buffer; the iterator form built a closure per // source. := len(.sources) := max(min(, parallel.Workers()), 1) := ( + - 1) / varsync.WaitGroup .Add()for := range { , := *, min((+1)*, )gofunc() {defer .Done() := map[NodeID]uint32{}var []NodeIDfor := ; < ; ++ { := .sources[]clear() = .g.AppendNeighborsMatch([:0], , .dir, .m)for , := range { , := , truefor , := range { , := .g.FirstNeighborMatch(, ., .)if ! { = falsebreak } = }if { []++ } } := uint32(0)for , := range { = max(, ) } [] = SourceSize{Source: , Size: } } }() } .Wait()return}// TopBySize is the n sources with the largest cohorts, size descending.// Ties break by the tieKey node property (read as i64, ascending) when// non-empty, else by source id ascending -- always deterministic.func ( *NeighborGroups) ( int, string) []SourceSize { := .Sizes()if != "" {// Resolve each source's tie value once, not inside the comparator. := make([]int64, len())for , := range { [] = .g.Prop(.Source, ).I64Or(0) }typestruct {SourceSizeint64 } := make([], len())for , := range { [] = {: , : []} }// Generic stable sort: sort.SliceStable's reflection-based // swapper dominated hot kernels (typedmemmove per swap).slices.SortStableFunc(, func(, ) int {if ..Size != ..Size {returncmp.Compare(..Size, ..Size) }returncmp.Compare(., .) }) := make([]SourceSize, 0, min(, len()))for , := range [:min(, len())] { = append(, .) }return }slices.SortStableFunc(, func(, SourceSize) int {if .Size != .Size {returncmp.Compare(.Size, .Size) }returncmp.Compare(.Source, .Source) })return [:min(, len())]}
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.