package unit; import java.util.*; /** * Unit test for graphhopper-0001 / graphhopper-0002: * AlternativeRouteCH / AlternativeRouteEdgeCH — IntArrayList.contains() O(P) inside edge loop. * * Simulates the sharedDistanceWithShortest() pattern: * for each edge (E edges): * nodeList.contains(baseNode) // O(P) linear scan on slow path * nodeList.contains(adjNode) // O(P) linear scan on slow path * * Compile: javac -d . AlternativeRouteCHNodesContainsAlgorithm.java * Run: java -ea unit.AlternativeRouteCHNodesContainsAlgorithm */ public class AlternativeRouteCHNodesContainsAlgorithm { static int checkCount = 0; static void check(String desc, boolean cond) { checkCount++; if (!cond) { System.out.println("FAIL [" + checkCount + "]: " + desc); } else { System.out.println("PASS [" + checkCount + "]: " + desc); } } // ------------------------------------------------------------------------- // Slow path: simulate IntArrayList membership — linear scan O(P) // ------------------------------------------------------------------------- /** Simulated ArrayList-backed node list (mirrors IntArrayList) */ static class SlowNodeList { final int[] data; int ops = 0; SlowNodeList(int[] nodes) { this.data = nodes; } boolean contains(int v) { for (int n : data) { ops++; if (n == v) return true; } return false; } } static long sharedDistanceSlow(int[] pathNodes, int[] edgeBase, int[] edgeAdj) { SlowNodeList nodeList = new SlowNodeList(pathNodes); long sharedCount = 0; for (int i = 0; i < edgeBase.length; i++) { if (nodeList.contains(edgeBase[i]) && nodeList.contains(edgeAdj[i])) { sharedCount++; } } return nodeList.ops; } // ------------------------------------------------------------------------- // Fast path: simulate IntScatterSet membership — hash lookup O(1) // ------------------------------------------------------------------------- static class FastNodeSet { final Set data; int ops = 0; FastNodeSet(int[] nodes) { data = new HashSet<>(nodes.length * 2); for (int n : nodes) data.add(n); } boolean contains(int v) { ops++; return data.contains(v); } } static long sharedDistanceFast(int[] pathNodes, int[] edgeBase, int[] edgeAdj) { FastNodeSet nodeSet = new FastNodeSet(pathNodes); long sharedCount = 0; for (int i = 0; i < edgeBase.length; i++) { if (nodeSet.contains(edgeBase[i]) && nodeSet.contains(edgeAdj[i])) { sharedCount++; } } return nodeSet.ops; } // ------------------------------------------------------------------------- // nodesInCurrentAlternativeSetContains pattern: // for each alternative: nodeList.contains(v) // called for each edge endpoint (2 × E calls), each doing O(P) scan // ------------------------------------------------------------------------- static long nodesInAltSetSlow(int[][] altNodeArrays, int[] edgeBase, int[] edgeAdj) { SlowNodeList[] lists = new SlowNodeList[altNodeArrays.length]; for (int i = 0; i < altNodeArrays.length; i++) { lists[i] = new SlowNodeList(altNodeArrays[i]); } long totalOps = 0; for (int i = 0; i < edgeBase.length; i++) { boolean baseFound = false; for (SlowNodeList list : lists) { if (list.contains(edgeBase[i])) { baseFound = true; break; } } if (baseFound) { for (SlowNodeList list : lists) { list.contains(edgeAdj[i]); } } } for (SlowNodeList list : lists) totalOps += list.ops; return totalOps; } static long nodesInAltSetFast(int[][] altNodeArrays, int[] edgeBase, int[] edgeAdj) { FastNodeSet[] sets = new FastNodeSet[altNodeArrays.length]; for (int i = 0; i < altNodeArrays.length; i++) { sets[i] = new FastNodeSet(altNodeArrays[i]); } long totalOps = 0; for (int i = 0; i < edgeBase.length; i++) { boolean baseFound = false; for (FastNodeSet set : sets) { if (set.contains(edgeBase[i])) { baseFound = true; break; } } if (baseFound) { for (FastNodeSet set : sets) { set.contains(edgeAdj[i]); } } } for (FastNodeSet set : sets) totalOps += set.ops; return totalOps; } // ------------------------------------------------------------------------- // Build test data // ------------------------------------------------------------------------- /** Build a path of N nodes (linear: 0→1→2→...→N-1) */ static int[] buildPathNodes(int n) { int[] nodes = new int[n]; for (int i = 0; i < n; i++) nodes[i] = i; return nodes; } /** * Build E edges where half share path nodes (from front of path) and * half are outside the path (node ids >= n). */ static int[][] buildEdges(int e, int n) { int[] base = new int[e]; int[] adj = new int[e]; for (int i = 0; i < e; i++) { if (i < e / 2) { // edge between two path nodes — both contained base[i] = i % n; adj[i] = (i + 1) % n; } else { // edge outside the path — not contained base[i] = n + i; adj[i] = n + i + 1; } } return new int[][]{base, adj}; } public static void main(String[] args) { System.out.println("=== graphhopper-0001/0002: AlternativeRouteCH nodes.contains() ===\n"); // --- Test 1: sharedDistanceWithShortest — small N to verify correctness --- { int P = 10, E = 10; int[] path = buildPathNodes(P); int[][] edges = buildEdges(E, P); long slowOps = sharedDistanceSlow(path, edges[0], edges[1]); long fastOps = sharedDistanceFast(path, edges[0], edges[1]); check("small: slow_ops > 0", slowOps > 0); check("small: fast_ops > 0", fastOps > 0); check("small: slow >= fast", slowOps >= fastOps); } // --- Test 2: sharedDistanceWithShortest — large N to measure ratio --- { int P = 800, E = 1000; int[] path = buildPathNodes(P); int[][] edges = buildEdges(E, P); long slowOps = sharedDistanceSlow(path, edges[0], edges[1]); long fastOps = sharedDistanceFast(path, edges[0], edges[1]); // Slow: each .contains() scans up to P=800 nodes; 2 calls per edge × E edges // worst-case slow_ops ≈ 2 × E × P / 2 (half edges found early, half scan all) // minimum triangular-ish: slow_ops >> fast_ops by factor of P long expectedMinSlowOps = (long) E * P / 4; // conservative lower bound double ratio = (double) slowOps / fastOps; check("large: slow_ops >= E*P/4 (" + slowOps + " >= " + expectedMinSlowOps + ")", slowOps >= expectedMinSlowOps); check("large: fast_ops <= 2*E (" + fastOps + " <= " + (2L * E) + ")", fastOps <= 2L * E); check("large: ratio >= 10x (actual " + String.format("%.1f", ratio) + "x)", ratio >= 10.0); System.out.println(" slow_ops=" + slowOps + " fast_ops=" + fastOps + " ratio=" + String.format("%.0f", ratio) + "x"); } // --- Test 3: nodesInCurrentAlternativeSetContains — 3 alternatives --- { int P = 600, E = 800, A = 3; int[][] altNodes = new int[A][]; for (int i = 0; i < A; i++) altNodes[i] = buildPathNodes(P + i * 50); int[][] edges = buildEdges(E, P); long slowOps = nodesInAltSetSlow(altNodes, edges[0], edges[1]); long fastOps = nodesInAltSetFast(altNodes, edges[0], edges[1]); double ratio = (double) slowOps / fastOps; check("altset: slow_ops > 0 (" + slowOps + ")", slowOps > 0); check("altset: fast_ops > 0 (" + fastOps + ")", fastOps > 0); check("altset: ratio >= 10x (actual " + String.format("%.1f", ratio) + "x)", ratio >= 10.0); System.out.println(" slow_ops=" + slowOps + " fast_ops=" + fastOps + " ratio=" + String.format("%.0f", ratio) + "x (A=" + A + ")"); } // --- Test 4: verify correctness — both paths return same shared count --- { int P = 20, E = 20; int[] path = buildPathNodes(P); int[][] edges = buildEdges(E, P); // Run both paths and count how many edges are "shared" (both endpoints in path) SlowNodeList slowList = new SlowNodeList(path); FastNodeSet fastSet = new FastNodeSet(path); int slowShared = 0, fastShared = 0; for (int i = 0; i < E; i++) { if (slowList.contains(edges[0][i]) && slowList.contains(edges[1][i])) slowShared++; if (fastSet.contains(edges[0][i]) && fastSet.contains(edges[1][i])) fastShared++; } check("correctness: slow and fast agree on shared count (" + slowShared + " == " + fastShared + ")", slowShared == fastShared); } System.out.println("\n" + checkCount + "/" + checkCount + " checks complete"); } }