Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

156 lines
3.9 KiB
Go

package analysis
import (
"sort"
"github.com/zzet/gortex/internal/graph"
)
// KCoreHit is one row of the k-core decomposition output: a node
// plus its k-degree (the largest k for which it stays in the
// k-core after iterative degree-< k pruning). High k-degree
// signals a node sits inside a densely connected core; a chain of
// leaves all have k-degree 1, a triangle has k-degree 2, a
// 4-clique has k-degree 3.
type KCoreHit struct {
NodeID string
KDegree int
}
// KCoreOptions filters the working set. Empty NodeKinds /
// EdgeKinds means "all kinds". Edges are treated as undirected
// (k-core is defined on undirected graphs).
type KCoreOptions struct {
NodeKinds []graph.NodeKind
EdgeKinds []graph.EdgeKind
}
// ComputeKCore returns the k-core decomposition of g. Classic
// algorithm — Batagelj & Zaversnik 2003, O(V + E):
//
// 1. compute every node's undirected degree
// 2. process nodes in degree-ascending order
// 3. when a node is removed, decrement its still-present
// neighbours' degrees so they can be picked up at the right
// level
//
// Used as the fallback when the backing graph.Store does not
// implement graph.KCorer.
func ComputeKCore(g graph.Store, opts KCoreOptions) []KCoreHit {
if g == nil {
return nil
}
nodeAllow := makeComponentKindAllow(opts.NodeKinds)
edgeAllow := makeComponentEdgeAllow(opts.EdgeKinds)
// Dense index over allowed nodes.
nodes := g.AllNodes()
idx := make(map[string]int, len(nodes))
dense := make([]string, 0, len(nodes))
for _, n := range nodes {
if n == nil || !nodeAllow(n.Kind) {
continue
}
idx[n.ID] = len(dense)
dense = append(dense, n.ID)
}
if len(dense) == 0 {
return nil
}
// Undirected adjacency; dedupe self-loops + parallel edges.
type edge struct{ a, b int }
seenEdge := make(map[edge]bool)
adj := make([][]int, len(dense))
for _, e := range g.AllEdges() {
if e == nil || !edgeAllow(e.Kind) {
continue
}
i, ok1 := idx[e.From]
j, ok2 := idx[e.To]
if !ok1 || !ok2 || i == j {
continue
}
key := edge{i, j}
if i > j {
key = edge{j, i}
}
if seenEdge[key] {
continue
}
seenEdge[key] = true
adj[i] = append(adj[i], j)
adj[j] = append(adj[j], i)
}
n := len(dense)
degree := make([]int, n)
maxDeg := 0
for i := range dense {
degree[i] = len(adj[i])
if degree[i] > maxDeg {
maxDeg = degree[i]
}
}
// Bucket sort by degree (Batagelj & Zaversnik). bucket[d]
// holds dense-indices currently at degree d; pos[v] is v's
// position in its bucket; vertOrder is the global processing
// order populated as we drain the buckets.
bucket := make([][]int, maxDeg+1)
pos := make([]int, n)
for v, d := range degree {
pos[v] = len(bucket[d])
bucket[d] = append(bucket[d], v)
}
kdeg := make([]int, n)
processed := make([]bool, n)
for d := 0; d <= maxDeg; d++ {
for len(bucket[d]) > 0 {
// Pop the back of bucket[d] (O(1)).
v := bucket[d][len(bucket[d])-1]
bucket[d] = bucket[d][:len(bucket[d])-1]
if processed[v] {
continue
}
processed[v] = true
kdeg[v] = d
for _, w := range adj[v] {
if processed[w] {
continue
}
if degree[w] > d {
// Move w one bucket down.
old := degree[w]
// O(1) removal: swap with the back element
// of the old bucket and adjust its pos.
i := pos[w]
last := len(bucket[old]) - 1
if i != last {
other := bucket[old][last]
bucket[old][i] = other
pos[other] = i
}
bucket[old] = bucket[old][:last]
degree[w] = old - 1
pos[w] = len(bucket[degree[w]])
bucket[degree[w]] = append(bucket[degree[w]], w)
}
}
}
}
out := make([]KCoreHit, 0, n)
for v, id := range dense {
out = append(out, KCoreHit{NodeID: id, KDegree: kdeg[v]})
}
sort.Slice(out, func(i, j int) bool {
if out[i].KDegree != out[j].KDegree {
return out[i].KDegree > out[j].KDegree
}
return out[i].NodeID < out[j].NodeID
})
return out
}