package unit; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; /** * Models Types.interfaceCandidates() dedup logic — defective vs fixed. * * DEFECT (javac-0006): when collecting interface method candidates from a * membersClosure walk, duplicates are filtered with List.contains() which * is O(S) on the javac singly-linked List. As S candidates accumulate the * S-th insertion requires scanning S-1 prior entries → O(S²) total. * * FIX: maintain a parallel LinkedHashSet for O(1) dedup while still * building the prepend-ordered List for callers. * * Test topology — diamond interface symbol stream: * Simulates membersClosure returning S unique symbols followed by S-1 * duplicates (a realistic diamond hierarchy where each interface method * is inherited through two paths and therefore appears twice in the * closure walk). * * SLOW: 1 + 2 + ... + (S-1) ≈ S²/2 contains scans (duplicates hit the * entire existing list before being rejected). * FAST: S add-to-HashSet calls, O(1) each. */ public class InterfaceCandidatesAlgorithm { // ── Result ─────────────────────────────────────────────────────────────── public static class Result { public final List candidates; public final long ops; public Result(List candidates, long ops) { this.candidates = candidates; this.ops = ops; } } // ── Defective ───────────────────────────────────────────────────────── /** * Mirrors: List candidates2 = List.nil(); * for (Symbol s : closureSymbols) { * if (!candidates2.contains(s)) { * candidates2 = candidates2.prepend(s); * } * } * * Uses ArrayList to simulate the linear-scan javac List.contains() cost. */ public static Result slow(List symbols) { long[] ops = {0}; // simulate javac List with an ArrayList for linear scan ArrayList candidatesList = new ArrayList<>(); for (String sym : symbols) { // linear scan — each element of candidatesList is one "op" boolean found = false; for (String existing : candidatesList) { ops[0]++; if (existing.equals(sym)) { found = true; break; } } if (!found) { candidatesList.add(0, sym); // prepend } } return new Result(new ArrayList<>(candidatesList), ops[0]); } // ── Fixed ───────────────────────────────────────────────────────────── /** * Mirrors: LinkedHashSet seen = new LinkedHashSet<>(); * List candidates2 = List.nil(); * for (Symbol s : closureSymbols) { * if (seen.add(s)) { * candidates2 = candidates2.prepend(s); * } * } * * O(1) HashSet add for dedup; maintains List for ordering contract. */ public static Result fast(List symbols) { long[] ops = {0}; LinkedHashSet seen = new LinkedHashSet<>(); ArrayList candidatesList = new ArrayList<>(); for (String sym : symbols) { ops[0]++; // one hash op if (seen.add(sym)) { candidatesList.add(0, sym); // prepend } } return new Result(new ArrayList<>(candidatesList), ops[0]); } // ── Test harness ───────────────────────────────────────────────────── /** * Build a diamond-hierarchy symbol stream: * S unique symbols followed by S-1 of those same symbols again * (simulating two inheritance paths resolving the same methods). */ static List diamondSymbols(int s) { List syms = new ArrayList<>(2 * s - 1); for (int i = 0; i < s; i++) { syms.add("method_" + i); } // duplicate path: re-emit first S-1 symbols (the "other parent") for (int i = 0; i < s - 1; i++) { syms.add("method_" + i); } return syms; } public static void main(String[] args) { int[] sizes = {100, 200, 400}; int passes = 0; int fails = 0; for (int s : sizes) { List syms = diamondSymbols(s); Result slow = slow(syms); Result fast = fast(syms); // correctness: both must produce the same candidate set LinkedHashSet slowSet = new LinkedHashSet<>(slow.candidates); LinkedHashSet fastSet = new LinkedHashSet<>(fast.candidates); boolean correct = slowSet.equals(fastSet) && slow.candidates.size() == s; // ratio: slow ops should be >> fast ops double ratio = (double) slow.ops / fast.ops; boolean ratioOk = ratio >= 5.0; if (correct && ratioOk) { System.out.printf("PASS S=%4d slow=%8d fast=%6d ratio=%6.1fx%n", s, slow.ops, fast.ops, ratio); passes++; } else { System.out.printf("FAIL S=%4d correct=%b ratio=%.1fx (need>=5)%n", s, correct, ratio); fails++; } } System.out.printf("%n%d/%d PASS%n", passes, passes + fails); if (fails > 0) System.exit(1); } }