129 lines
4.7 KiB
Java
129 lines
4.7 KiB
Java
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<Integer> 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<Integer> 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<Integer> typesToVisit = new ArrayList<>();
|
|
Set<Integer> 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<Result> 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);
|
|
}
|
|
}
|
|
}
|