package unit; import java.util.*; /** * Unit test for ros2-0002: NodeParameters::list_parameters() O(P²) → O(P) * * Simulates prefix deduplication in NodeParameters::list_parameters(): * Defective: std::find on result.prefixes (grows to P) inside loop over P parameters * Fixed: unordered_set deduplication O(1) insert * * Compile: javac -d . ListParametersAlgorithm.java * Run: java -ea unit.ListParametersAlgorithm */ public class ListParametersAlgorithm { static class Result { final List names = new ArrayList<>(); final List prefixes = new ArrayList<>(); } // ── defective implementation ────────────────────────────────────────────── static Result defectiveListParameters(List parameterNames) { Result result = new Result(); final char separator = '.'; for (String name : parameterNames) { result.names.add(name); int lastSep = name.lastIndexOf(separator); if (lastSep != -1) { String prefix = name.substring(0, lastSep); // O(P) linear scan to deduplicate prefixes boolean found = false; for (String p : result.prefixes) { if (p.equals(prefix)) { found = true; break; } } if (!found) result.prefixes.add(prefix); } } return result; } // ── fixed implementation ────────────────────────────────────────────────── static Result fixedListParameters(List parameterNames) { Result result = new Result(); final char separator = '.'; Set seenPrefixes = new HashSet<>(); for (String name : parameterNames) { result.names.add(name); int lastSep = name.lastIndexOf(separator); if (lastSep != -1) { String prefix = name.substring(0, lastSep); if (seenPrefixes.add(prefix)) { // O(1) insert + dedup result.prefixes.add(prefix); } } } return result; } // ── tests ───────────────────────────────────────────────────────────────── static int pass = 0, total = 0; static void assertTrue(String name, boolean cond) { total++; if (cond) { pass++; System.out.println("PASS " + name); } else System.out.println("FAIL " + name); } // Build nav2-style parameter names: namespace.group.param static List buildNav2Params(int namespaces, int groups, int paramsPerGroup) { List names = new ArrayList<>(); for (int ns = 0; ns < namespaces; ns++) { for (int g = 0; g < groups; g++) { for (int p = 0; p < paramsPerGroup; p++) { names.add("ns" + ns + ".group" + g + ".param" + p); } } } return names; } public static void main(String[] args) { // Test 1: empty parameters → empty result { Result d = defectiveListParameters(Collections.emptyList()); Result f = fixedListParameters(Collections.emptyList()); assertTrue("empty-defective", d.names.isEmpty() && d.prefixes.isEmpty()); assertTrue("empty-fixed", f.names.isEmpty() && f.prefixes.isEmpty()); } // Test 2: parameters with no separator → no prefixes { List params = Arrays.asList("foo", "bar", "baz"); Result d = defectiveListParameters(params); Result f = fixedListParameters(params); assertTrue("no-sep-defective", d.prefixes.isEmpty() && d.names.size() == 3); assertTrue("no-sep-fixed", f.prefixes.isEmpty() && f.names.size() == 3); } // Test 3: all same prefix → only one prefix entry { List params = Arrays.asList("nav2.speed", "nav2.timeout", "nav2.max_vel"); Result d = defectiveListParameters(params); Result f = fixedListParameters(params); assertTrue("same-prefix-dedup-defective", d.prefixes.size() == 1 && d.prefixes.get(0).equals("nav2")); assertTrue("same-prefix-dedup-fixed", f.prefixes.size() == 1 && f.prefixes.get(0).equals("nav2")); } // Test 4: multiple distinct prefixes { List params = Arrays.asList( "nav2.speed", "nav2.timeout", "moveit.planning_time", "moveit.max_vel", "slam.resolution"); Result d = defectiveListParameters(params); Result f = fixedListParameters(params); assertTrue("multi-prefix-defective", d.prefixes.size() == 3); assertTrue("multi-prefix-fixed", f.prefixes.size() == 3); assertTrue("multi-prefix-names-match", d.names.equals(f.names)); // prefixes sets must be equal (order may differ) assertTrue("multi-prefix-sets-match", new HashSet<>(d.prefixes).equals(new HashSet<>(f.prefixes))); } // Test 5: nav2-style — 3 namespaces, 10 groups, 5 params each = 150 params, 30 unique prefixes { List params = buildNav2Params(3, 10, 5); Result d = defectiveListParameters(params); Result f = fixedListParameters(params); assertTrue("nav2-names-count-defective", d.names.size() == 150); assertTrue("nav2-names-count-fixed", f.names.size() == 150); // Each ns.group is a unique "ns_X.group_Y" prefix → 3*10 = 30 unique prefixes assertTrue("nav2-prefix-count-defective", d.prefixes.size() == 30); assertTrue("nav2-prefix-count-fixed", f.prefixes.size() == 30); assertTrue("nav2-prefix-sets-match", new HashSet<>(d.prefixes).equals(new HashSet<>(f.prefixes))); } // Test 6: performance at P=500 (500 unique prefixes worst case) { List params = new ArrayList<>(); for (int i = 0; i < 500; i++) params.add("group" + i + ".param" + i); long t0 = System.nanoTime(); for (int r = 0; r < 200; r++) defectiveListParameters(params); long tDef = System.nanoTime() - t0; t0 = System.nanoTime(); for (int r = 0; r < 200; r++) fixedListParameters(params); long tFix = System.nanoTime() - t0; double ratio = (double) tDef / tFix; System.out.printf(" Perf P=500 unique: defective=%.1fms fixed=%.1fms ratio=%.1fx%n", tDef / 1e6, tFix / 1e6, ratio); assertTrue("perf-speedup", ratio > 3.0); } System.out.println(pass + "/" + total + " PASS"); assert pass == total : pass + "/" + total + " passed"; } }