package unit; import java.util.*; /** * ElixirMixTopoSortAlgorithm — CWE-407 test for elixir-0001 * * Models Mix.Dep.Converger.topological_sort/1: * slow: Enum.map(apps, fn app -> Enum.find(deps, ...) end) — O(N²) * fast: Map.new(deps, ...) then Enum.map(apps, dep_index[app]) — O(N) * * Test: for N deps, slow path does N*N list scans; fast path does N map lookups. */ public class ElixirMixTopoSortAlgorithm { // Simulate a Mix.Dep struct — just an app name (atom) and an index static class MixDep { final String app; MixDep(String app) { this.app = app; } } // --- SLOW: O(N²) --- // Enum.map(apps, fn app -> Enum.find(deps, fn dep -> dep.app == app end) end) static class SlowTopoSort { long comparisons = 0; List sort(List apps, List deps) { List result = new ArrayList<>(apps.size()); for (String app : apps) { // Enum.find — linear scan MixDep found = null; for (MixDep dep : deps) { comparisons++; if (dep.app.equals(app)) { found = dep; break; } } result.add(found); } return result; } } // --- FAST: O(N) --- // dep_index = Map.new(deps, fn dep -> {dep.app, dep} end) // Enum.map(apps, fn app -> dep_index[app] end) static class FastTopoSort { long lookups = 0; List sort(List apps, List deps) { // Build index: O(N) Map index = new HashMap<>(deps.size() * 2); for (MixDep dep : deps) { index.put(dep.app, dep); } // Map lookup: O(1) each List result = new ArrayList<>(apps.size()); for (String app : apps) { lookups++; result.add(index.get(app)); } return result; } } static List makeDeps(int n) { List deps = new ArrayList<>(n); for (int i = 0; i < n; i++) deps.add(new MixDep("dep_" + i)); return deps; } static List makeApps(List deps) { // Simulate topsort returning atoms in some order List apps = new ArrayList<>(deps.size()); for (MixDep d : deps) apps.add(d.app); Collections.shuffle(apps, new Random(42)); return apps; } public static void main(String[] args) { int[] sizes = {50, 100, 200, 400}; System.out.println("ElixirMixTopoSortAlgorithm — elixir-0001"); System.out.println(" Pattern: Enum.map + Enum.find(deps) — O(N²) vs Map index — O(N)"); System.out.println(); int passed = 0; int total = 0; for (int n : sizes) { List deps = makeDeps(n); List apps = makeApps(deps); SlowTopoSort slow = new SlowTopoSort(); FastTopoSort fast = new FastTopoSort(); List slowResult = slow.sort(apps, deps); List fastResult = fast.sort(apps, deps); // Verify both produce same order boolean same = true; for (int i = 0; i < apps.size(); i++) { if (!slowResult.get(i).app.equals(fastResult.get(i).app)) { same = false; break; } } // slow should do ~N*N comparisons (worst case); fast should do exactly N lookups long slowCmp = slow.comparisons; long fastLkp = fast.lookups; // For random order, average Enum.find scan is N/2 per element → ~N²/2 total // Fast is always N lookups boolean slowIsQuadratic = slowCmp >= (long) n * n / 4; boolean fastIsLinear = fastLkp == n; total += 3; if (same) { System.out.println("PASS N=" + n + ": results match"); passed++; } else { System.out.println("FAIL N=" + n + ": result mismatch"); } if (slowIsQuadratic) { System.out.println("PASS N=" + n + ": slow O(N²) comparisons=" + slowCmp + " >= N²/4=" + (n * n / 4)); passed++; } else { System.out.println("FAIL N=" + n + ": slow not quadratic, comparisons=" + slowCmp); } if (fastIsLinear) { System.out.println("PASS N=" + n + ": fast O(N) lookups=" + fastLkp + " == N=" + n); passed++; } else { System.out.println("FAIL N=" + n + ": fast not linear, lookups=" + fastLkp); } } System.out.println(); System.out.println(passed + "/" + total + " PASS"); if (passed != total) { throw new AssertionError(passed + "/" + total + " tests passed"); } } }