75 lines
2.7 KiB
Diff
75 lines
2.7 KiB
Diff
# UNDF: UNDF-2026-000000225
|
||
--- a/rules/group.go
|
||
+++ b/rules/group.go
|
||
@@ -1083,6 +1083,17 @@ func (m dependencyMap) dependents(r Rule) []Rule {
|
||
// dependencyMap maps a Rule to the slice of rules that depend on it (its "dependents").
|
||
type dependencyMap map[Rule][]Rule
|
||
|
||
+// inverseDependencyMap is the reverse index: maps a Rule to the set of rules it depends on
|
||
+// (i.e. its "dependencies"). Built alongside dependencyMap so that dependencies() is O(1)
|
||
+// instead of O(R×D) — a linear scan over the full map that makes AnalyseRules O(R²).
|
||
+type inverseDependencyMap map[Rule][]Rule
|
||
+
|
||
+// buildInverseMap creates an inverseDependencyMap from a dependencyMap.
|
||
+// Cost: O(R×D), paid once at buildDependencyMap time instead of O(R) times in AnalyseRules.
|
||
+func buildInverseMap(forward dependencyMap) inverseDependencyMap {
|
||
+ inv := make(inverseDependencyMap, len(forward))
|
||
+ for rule, dependents := range forward {
|
||
+ for _, dep := range dependents {
|
||
+ inv[dep] = append(inv[dep], rule)
|
||
+ }
|
||
+ }
|
||
+ return inv
|
||
+}
|
||
+
|
||
// dependents returns the rules which use the output of the given rule as one of their inputs.
|
||
func (m dependencyMap) dependents(r Rule) []Rule {
|
||
return m[r]
|
||
@@ -1090,14 +1101,12 @@ func (m dependencyMap) dependents(r Rule) []Rule {
|
||
// dependencies returns the rules on which the given rule is dependent for input.
|
||
-func (m dependencyMap) dependencies(r Rule) []Rule {
|
||
+func (m dependencyMap) dependencies(r Rule, inv inverseDependencyMap) []Rule {
|
||
if len(m) == 0 {
|
||
return []Rule{}
|
||
}
|
||
|
||
- var dependencies []Rule
|
||
- for rule, dependents := range m {
|
||
- // O(R×D): scans every entry in the map, then slices.Contains on dependents.
|
||
- if slices.Contains(dependents, r) {
|
||
- dependencies = append(dependencies, rule)
|
||
- }
|
||
- }
|
||
-
|
||
- return dependencies
|
||
+ // O(1): direct map lookup into the pre-built inverse index.
|
||
+ return inv[r]
|
||
}
|
||
|
||
// isIndependent determines whether the given rule is not dependent on another rule for its input, nor is any other rule
|
||
// dependent on its output.
|
||
-func (m dependencyMap) isIndependent(r Rule) bool {
|
||
+func (m dependencyMap) isIndependent(r Rule, inv inverseDependencyMap) bool {
|
||
if m == nil {
|
||
return false
|
||
}
|
||
|
||
- return len(m.dependents(r)) == 0 && len(m.dependencies(r)) == 0
|
||
+ return len(m.dependents(r)) == 0 && len(m.dependencies(r, inv)) == 0
|
||
}
|
||
--- a/rules/manager.go
|
||
+++ b/rules/manager.go
|
||
@@ -505,8 +505,11 @@ func (ruleDependencyController) AnalyseRules(rules []Rule) {
|
||
if depMap == nil {
|
||
return
|
||
}
|
||
|
||
+ // Build the inverse map once — O(R×D) — to make per-rule dependencies() calls O(1).
|
||
+ inv := buildInverseMap(depMap)
|
||
+
|
||
for _, r := range rules {
|
||
r.SetDependentRules(depMap.dependents(r))
|
||
- r.SetDependencyRules(depMap.dependencies(r))
|
||
+ r.SetDependencyRules(depMap.dependencies(r, inv))
|
||
}
|
||
}
|