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 found; public final long comparisons; Result(List 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 nodes; private long comparisons = 0; public DefectiveFinder(List 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 index; private long comparisons = 0; public FixedFinder(List 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 nodes = buildNodeList(n); DefectiveFinder finder = new DefectiveFinder(nodes); List 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 nodes = buildNodeList(n); FixedFinder finder = new FixedFinder(nodes); List 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 buildNodeList(int n) { List nodes = new ArrayList<>(); for (int i = 0; i < n; i++) nodes.add(new Node("N" + i)); return nodes; } }