package unit; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Models InferenceContext.notifyChange() diff-hoist defect — defective vs fixed. * * DEFECT (javac-0007): inferencevars.diff(inferredVars) is recomputed on * every iteration of the freeTypeListeners loop. * * void notifyChange(List inferredVars) { * for (entry : freeTypeListeners.entrySet()) { * if (!Type.containsAny(entry.getValue(), * inferencevars.diff(inferredVars))) { // ← rebuilt L times * ... * } * } * } * * List.diff() iterates inferredVars (M elements) and for each scans * inferencevars (N elements) → O(N×M) per call. Called L times: O(L×N×M). * * FIX: hoist the diff() before the loop. * * List remainingVars = inferencevars.diff(inferredVars); // once * for (entry : freeTypeListeners.entrySet()) { * if (!Type.containsAny(entry.getValue(), remainingVars)) { * ... * } * } * * Complexity: * Defective: O(L × N × M) * Fixed: O(N×M + L×V) * * Test topology: * N = inferencevars (type-variable list length) * M = inferredVars (resolved subset — set to N/2 for worst-case diff work) * L = listener count * * The op-counter measures diff() recomputation cost: * SLOW: diff() called L times → L × N × M ops * FAST: diff() called once → N × M ops (then L cheap list-scan checks) */ public class NotifyChangeAlgorithm { // ── Result ─────────────────────────────────────────────────────────────── public static class Result { public final int notifiedCount; // how many listeners fired public final long ops; public Result(int notifiedCount, long ops) { this.notifiedCount = notifiedCount; this.ops = ops; } } // ── helpers ────────────────────────────────────────────────────────────── /** * Simulates List.diff(that): iterates every element of 'from' (N items), * for each does a linear scan of 'that' (M items). Returns elements of * 'from' not present in 'that'. */ static List diff(List from, List that, long[] ops) { List result = new ArrayList<>(); for (String f : from) { boolean found = false; for (String t : that) { ops[0]++; if (f.equals(t)) { found = true; break; } } if (!found) result.add(f); } return result; } /** * Simulates Type.containsAny(ts1, ts2): returns true if any element of * ts1 is present in ts2. */ static boolean containsAny(List ts1, List ts2) { for (String t : ts1) { for (String s : ts2) { if (t.equals(s)) return true; } } return false; } // ── Defective ───────────────────────────────────────────────────────── /** * diff() recomputed inside every listener iteration. */ public static Result slow(List inferencevars, List inferredVars, Map> listeners) { long[] ops = {0}; int notified = 0; for (Map.Entry> entry : listeners.entrySet()) { // diff recomputed every iteration List remaining = diff(inferencevars, inferredVars, ops); if (!containsAny(entry.getValue(), remaining)) { notified++; } } return new Result(notified, ops[0]); } // ── Fixed ───────────────────────────────────────────────────────────── /** * diff() hoisted before the loop. */ public static Result fast(List inferencevars, List inferredVars, Map> listeners) { long[] ops = {0}; int notified = 0; List remaining = diff(inferencevars, inferredVars, ops); // once for (Map.Entry> entry : listeners.entrySet()) { if (!containsAny(entry.getValue(), remaining)) { notified++; } } return new Result(notified, ops[0]); } // ── Test harness ───────────────────────────────────────────────────── static List makeVars(String prefix, int n) { List vars = new ArrayList<>(n); for (int i = 0; i < n; i++) vars.add(prefix + i); return vars; } /** * Build listener map: L listeners each watching V variables from * inferencevars. We pick variables that are NOT in inferredVars so * that containsAny returns false (listener fires). */ static Map> makeListeners(List inferencevars, List inferredVars, int l, int v) { // build set of "remaining" vars (not inferred) to register listeners on List remaining = new ArrayList<>(); for (String iv : inferencevars) { if (!inferredVars.contains(iv)) remaining.add(iv); } Map> map = new LinkedHashMap<>(); for (int i = 0; i < l; i++) { List watched = new ArrayList<>(); // each listener watches v vars from remaining (cycling) for (int j = 0; j < v; j++) { watched.add(remaining.get((i + j) % remaining.size())); } map.put("listener_" + i, watched); } return map; } public static void main(String[] args) { // N=inferencevars, M=inferredVars (=N/2), L=listeners int[][] configs = { {40, 20, 40}, // N=40, M=20, L=40 {80, 40, 80}, // N=80, M=40, L=80 {160, 80, 160}, // N=160, M=80, L=160 }; int passes = 0; int fails = 0; for (int[] c : configs) { int n = c[0], m = c[1], l = c[2]; List inferencevars = makeVars("T", n); List inferredVars = makeVars("T", m); // T0..T(m-1) inferred Map> listeners = makeListeners(inferencevars, inferredVars, l, 3); Result slow = slow(inferencevars, inferredVars, listeners); Result fast = fast(inferencevars, inferredVars, listeners); // correctness: same notification count boolean correct = slow.notifiedCount == fast.notifiedCount; // ratio: slow should be >> fast double ratio = (double) slow.ops / Math.max(fast.ops, 1); boolean ratioOk = ratio >= 5.0; if (correct && ratioOk) { System.out.printf("PASS N=%4d M=%4d L=%4d slow=%10d fast=%8d ratio=%6.1fx%n", n, m, l, slow.ops, fast.ops, ratio); passes++; } else { System.out.printf("FAIL N=%4d M=%4d L=%4d correct=%b ratio=%.1fx (need>=5)%n", n, m, l, correct, ratio); fails++; } } System.out.printf("%n%d/%d PASS%n", passes, passes + fails); if (fails > 0) System.exit(1); } }