import java.util.*; /** * Unit test for cmake-0005: MergeOptions O(N*M) -> O(N) via hash set. * * Models cmQtAutoGen::MergeOptions() dedup behaviour: * - options present in baseOpts are updated in place (not re-added) * - options absent from baseOpts are appended as extraOpts * * Defect: std::find over baseOpts (O(M)) inside loop over newOpts (O(N)) = O(N*M). * Fix: unordered_set for membership check = O(1), total O(N). */ public class MergeOptionsAlgorithm { // --- Defective implementation (O(N*M)) --- static List mergeOptionsDefective(List baseOpts, List newOpts) { List base = new ArrayList<>(baseOpts); List extra = new ArrayList<>(); for (String newOpt : newOpts) { int idx = base.indexOf(newOpt); // O(M) linear scan -- the defect if (idx >= 0) { // option already present, update value-option successor if any // (simplified: just mark as seen) } else { extra.add(newOpt); } } base.addAll(extra); return base; } // --- Fixed implementation (O(N)) --- static List mergeOptionsFixed(List baseOpts, List newOpts) { List base = new ArrayList<>(baseOpts); Set baseSet = new HashSet<>(baseOpts); // O(M) build, O(1) lookup List extra = new ArrayList<>(); for (String newOpt : newOpts) { if (!baseSet.contains(newOpt)) { extra.add(newOpt); } } base.addAll(extra); return base; } // --- Correctness test --- static void assertEq(List a, List b, String msg) { if (!a.equals(b)) throw new AssertionError(msg + ": " + a + " != " + b); } static void testCorrectness() { List base = Arrays.asList("-fPIC", "-O2", "-Wall"); List newOpts = Arrays.asList("-O2", "-Wextra", "-fPIC"); List defectResult = mergeOptionsDefective(base, newOpts); List fixedResult = mergeOptionsFixed(base, newOpts); // Both should keep original base options and only add truly new ones // -Wextra is new; -O2 and -fPIC already exist List expected = Arrays.asList("-fPIC", "-O2", "-Wall", "-Wextra"); assertEq(defectResult, expected, "defective correctness"); assertEq(fixedResult, expected, "fixed correctness"); System.out.println("PASS testCorrectness"); } static void testEmptyNewOpts() { List base = Arrays.asList("-fPIC", "-O2"); List newOpts = Collections.emptyList(); List expected = Arrays.asList("-fPIC", "-O2"); assertEq(mergeOptionsFixed(base, newOpts), expected, "empty newOpts"); System.out.println("PASS testEmptyNewOpts"); } static void testAllNew() { List base = Arrays.asList("-fPIC"); List newOpts = Arrays.asList("-O2", "-Wall"); List result = mergeOptionsFixed(base, newOpts); if (!result.contains("-O2") || !result.contains("-Wall")) { throw new AssertionError("all-new options missing: " + result); } System.out.println("PASS testAllNew"); } // --- Performance benchmark --- static long benchmarkOps(int baseSize, int newSize) { List base = new ArrayList<>(); for (int i = 0; i < baseSize; i++) base.add("-opt" + i); List newOpts = new ArrayList<>(); // Half overlap, half new for (int i = 0; i < newSize / 2; i++) newOpts.add("-opt" + i); for (int i = 0; i < newSize / 2; i++) newOpts.add("-new" + i); // Defective: count linear scan operations long defectiveOps = 0; for (String newOpt : newOpts) { for (String b : base) { // simulates indexOf defectiveOps++; if (b.equals(newOpt)) break; } } return defectiveOps; } static void testBenchmark() { int BASE = 50; int NEW = 50; long defectiveOps = benchmarkOps(BASE, NEW); // Fixed: O(N) = NEW hash lookups long fixedOps = NEW; double ratio = (double) defectiveOps / fixedOps; System.out.printf("BENCH mergeOptions base=%d new=%d: defective=%d ops, fixed=%d ops, ratio=%.1fx%n", BASE, NEW, defectiveOps, fixedOps, ratio); if (ratio < 5.0) { throw new AssertionError("Expected ratio >= 5x, got " + ratio); } System.out.println("PASS testBenchmark (ratio >= 5x confirmed)"); } public static void main(String[] args) { testCorrectness(); testEmptyNewOpts(); testAllNew(); testBenchmark(); System.out.println("ALL PASS cmake-0005 MergeOptions"); } }