package unit; import java.util.*; /** * eclipse-jdt-0001: minimalErasedCandidatesAlgorithm BFS work-queue dedup * * Demonstrates O(N²) ArrayList.contains vs O(N) HashSet.add dedup when * building the supertype-hierarchy work-queue in Scope.minimalErasedCandidates. * * The real BFS (Scope.java ~4295-4383) walks a type's superinterfaces AND * superclass, calling typesToVisit.contains() for each candidate. In a deep * diamond interface graph each BFS step checks K candidates (K = branching * factor), so total contains-calls = K * N steps * avg-list-size N/2 = O(K*N²). * * This test models K=4 candidates per step (one superclass + 3 interfaces), * which matches the worst-case pattern in the Eclipse JDT source. * * SLOW: List typesToVisit + typesToVisit.contains(x) → O(N²) ops * FAST: List + parallel HashSet visitedSet + visitedSet.add(x) → O(N) ops */ public class MinimalErasedCandidatesAlgorithm { // Branching factor: number of supertypes added per BFS node // (mirrors: superclass + up to 3 interfaces in typical Java hierarchy) private static final int BRANCH = 4; // ── SLOW path (ArrayList dedup — mirrors JDT defect) ────────────────────── // Each BFS step checks BRANCH candidate supertypes via typesToVisit.contains static long slowBfs(int n) { List typesToVisit = new ArrayList<>(); typesToVisit.add(0); int max = 1; long ops = 0; for (int i = 0; i < max && max < n; i++) { int base = typesToVisit.get(i); // Simulate checking BRANCH supertypes per BFS node for (int k = 1; k <= BRANCH && max < n; k++) { int candidate = base + k; ops += typesToVisit.size(); // cost of ArrayList.contains if (!typesToVisit.contains(candidate)) { typesToVisit.add(candidate); max++; } } } return ops; } // ── FAST path (HashSet dedup — the fix) ─────────────────────────────────── static long fastBfs(int n) { List typesToVisit = new ArrayList<>(); Set visitedSet = new HashSet<>(); typesToVisit.add(0); visitedSet.add(0); int max = 1; long ops = 0; for (int i = 0; i < max && max < n; i++) { int base = typesToVisit.get(i); for (int k = 1; k <= BRANCH && max < n; k++) { int candidate = base + k; ops++; // O(1) hash lookup cost if (visitedSet.add(candidate)) { typesToVisit.add(candidate); max++; } } } return ops; } // ── Test harness ────────────────────────────────────────────────────────── static class Result { final String label; final int n; final long slowOps; final long fastOps; final boolean pass; Result(String label, int n, long slowOps, long fastOps) { this.label = label; this.n = n; this.slowOps = slowOps; this.fastOps = fastOps; double ratio = fastOps > 0 ? (double) slowOps / fastOps : slowOps; this.pass = ratio >= 5.0; } } public static void main(String[] args) { int[] sizes = {50, 100, 200, 500}; List results = new ArrayList<>(); for (int n : sizes) { long slow = slowBfs(n); long fast = fastBfs(n); results.add(new Result("N=" + n, n, slow, fast)); } System.out.println("eclipse-jdt-0001 MinimalErasedCandidatesAlgorithm"); System.out.println("=================================================="); System.out.printf("%-8s %10s %10s %8s %s%n", "N", "SLOW ops", "FAST ops", "Ratio", "PASS"); System.out.println("-".repeat(55)); int passed = 0; int total = results.size(); for (Result r : results) { double ratio = r.fastOps > 0 ? (double) r.slowOps / r.fastOps : r.slowOps; String status = r.pass ? "PASS" : "FAIL"; System.out.printf("%-8s %10d %10d %8.1fx %s%n", r.label, r.slowOps, r.fastOps, ratio, status); if (r.pass) passed++; } System.out.println("-".repeat(55)); System.out.printf("%d/%d PASS%n", passed, total); if (passed < total) { System.exit(1); } } }