java-topology/tests/support/FindNodeAlgorithm.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

122 lines
4.5 KiB
Java

package support;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Reference implementations of InferenceGraph.findNode() — defective and fixed.
*
* DEFECT 0002a (Infer.java:1850): findNode() scans an ArrayList linearly on
* every call. Called O(N²) times from buildStuckGraph()'s double loop.
* With uncached closure (defect 0002b), total = O(N³).
*
* Fix: replace the ArrayList scan with a HashMap built once at construction.
*
* Exact comparison counts when querying N nodes in order 0..N-1:
* defective: 1 + 2 + ... + N = N*(N+1)/2
* fixed: N (1 per HashMap.get())
*
* Derivation of defective count:
* Querying N0 first finds it at index 0 → 1 comparison.
* Querying N1 scans from the start: [N0, N1] → 2 comparisons.
* Querying N(k) scans k+1 elements → k+1 comparisons.
* Total: sum(k+1, k=0..N-1) = N*(N+1)/2.
*
* Growth when N doubles: defective ≈4x (quadratic), fixed ≈2x (linear).
*/
public class FindNodeAlgorithm {
public static class Node {
public final String label;
public Node(String label) { this.label = label; }
@Override public String toString() { return label; }
}
public static class Result {
public final List<Node> found;
public final long comparisons;
Result(List<Node> found, long comparisons) {
this.found = found;
this.comparisons = comparisons;
}
}
// ─── DEFECTIVE: O(N) ArrayList scan per call ────────────────────────────
// Mirrors Infer.java:1850 — iterates the nodes list looking for matching var.
public static class DefectiveFinder {
private final List<Node> nodes;
private long comparisons = 0;
public DefectiveFinder(List<Node> nodes) {
this.nodes = new ArrayList<>(nodes);
}
public Node findNode(String label) {
for (Node n : nodes) {
comparisons++;
if (n.label.equals(label)) return n;
}
return null;
}
public long getComparisons() { return comparisons; }
}
// ─── FIXED: O(1) HashMap lookup ─────────────────────────────────────────
// Build a label→node index once at construction; each findNode() = O(1).
public static class FixedFinder {
private final Map<String, Node> index;
private long comparisons = 0;
public FixedFinder(List<Node> nodes) {
index = new HashMap<>(nodes.size() * 2);
for (Node n : nodes) index.put(n.label, n);
}
public Node findNode(String label) {
comparisons++; // one HashMap.get() = O(1)
return index.get(label);
}
public long getComparisons() { return comparisons; }
}
// ─── Convenience runners ─────────────────────────────────────────────────
/**
* Query all N nodes in order 0..N-1 using the defective finder.
* Expected comparisons: N*(N+1)/2.
*/
public static Result queryAllDefective(int n) {
List<Node> nodes = buildNodeList(n);
DefectiveFinder finder = new DefectiveFinder(nodes);
List<Node> found = new ArrayList<>();
for (int i = 0; i < n; i++) found.add(finder.findNode("N" + i));
return new Result(found, finder.getComparisons());
}
/**
* Query all N nodes in order 0..N-1 using the fixed finder.
* Expected comparisons: N.
*/
public static Result queryAllFixed(int n) {
List<Node> nodes = buildNodeList(n);
FixedFinder finder = new FixedFinder(nodes);
List<Node> found = new ArrayList<>();
for (int i = 0; i < n; i++) found.add(finder.findNode("N" + i));
return new Result(found, finder.getComparisons());
}
// ─── Factory ─────────────────────────────────────────────────────────────
/** Returns a list of N nodes with labels "N0".."N(N-1)" in that order. */
public static List<Node> buildNodeList(int n) {
List<Node> nodes = new ArrayList<>();
for (int i = 0; i < n; i++) nodes.add(new Node("N" + i));
return nodes;
}
}