package unit; import java.util.*; /** * Unit test for valhalla-0001: * linkclassification.cc — IsSlipLane() nested linear scan O(F×R). * * Simulates the intersection-check loop: * for (node : reverse_nodes) // R iterations * std::find(forward_nodes.begin(), ..., node) // O(F) linear scan * * vs. the fixed version using unordered_set: * forward_set = unordered_set(forward_nodes) * for (node : reverse_nodes) // R iterations * forward_set.count(node) // O(1) hash lookup * * Compile: javac -d . IsSlipLaneAlgorithm.java * Run: java -ea unit.IsSlipLaneAlgorithm */ public class IsSlipLaneAlgorithm { static int checkCount = 0; static int passCount = 0; static void check(String desc, boolean cond) { checkCount++; if (!cond) { System.out.println("FAIL [" + checkCount + "]: " + desc); } else { passCount++; System.out.println("PASS [" + checkCount + "]: " + desc); } } // ------------------------------------------------------------------------- // Slow path: std::find linear scan over forward_nodes for each reverse node // ------------------------------------------------------------------------- static class SlowIntersectionFinder { int ops = 0; boolean find(int[] forwardNodes, int target) { for (int n : forwardNodes) { ops++; if (n == target) return true; } return false; } /** Returns intersection node index, or -1 if none */ int findIntersection(int[] forwardNodes, int[] reverseNodes) { for (int node : reverseNodes) { if (find(forwardNodes, node)) { return node; } } return -1; } } // ------------------------------------------------------------------------- // Fast path: unordered_set (HashSet) O(1) lookup // ------------------------------------------------------------------------- static class FastIntersectionFinder { int ops = 0; int findIntersection(int[] forwardNodes, int[] reverseNodes) { // Build set from forward_nodes once — O(F) Set forwardSet = new HashSet<>(forwardNodes.length * 2); for (int n : forwardNodes) forwardSet.add(n); for (int node : reverseNodes) { ops++; if (forwardSet.contains(node)) { return node; } } return -1; } } // ------------------------------------------------------------------------- // Build test data: two vectors of node IDs that share a suffix // forward_nodes: 0, 1, 2, ..., F-1 // reverse_nodes: F*2, F*2+1, ..., F*2+R-sharedSuffix-1, then F-sharedSuffix, ..., F-1 // Intersection is at node F-sharedSuffix (last shared node in forward, first in overlap) // If sharedSuffix == 0, no intersection exists (worst-case: scan all R × F) // ------------------------------------------------------------------------- static int[] buildForwardNodes(int F) { int[] nodes = new int[F]; for (int i = 0; i < F; i++) nodes[i] = i; return nodes; } static int[] buildReverseNodes(int F, int R, int sharedSuffix) { // First (R - sharedSuffix) nodes are unique to reverse, rest overlap with forward tail int[] nodes = new int[R]; int uniqueCount = R - sharedSuffix; for (int i = 0; i < uniqueCount; i++) { nodes[i] = F * 2 + i; // disjoint from forward } for (int i = 0; i < sharedSuffix; i++) { nodes[uniqueCount + i] = F - sharedSuffix + i; // overlaps with forward tail } return nodes; } public static void main(String[] args) { System.out.println("=== valhalla-0001: IsSlipLane forward_nodes linear scan ===\n"); // --- Test 1: correctness — both paths find the same intersection --- { int F = 20, R = 20, shared = 5; int[] fwd = buildForwardNodes(F); int[] rev = buildReverseNodes(F, R, shared); SlowIntersectionFinder slow = new SlowIntersectionFinder(); FastIntersectionFinder fast = new FastIntersectionFinder(); int slowResult = slow.findIntersection(fwd, rev); int fastResult = fast.findIntersection(fwd, rev); check("correctness: intersection found by both paths", slowResult != -1 && fastResult != -1); check("correctness: both agree on intersection node (" + slowResult + " == " + fastResult + ")", slowResult == fastResult); } // --- Test 2: correctness — no intersection case --- { int F = 20, R = 20, shared = 0; int[] fwd = buildForwardNodes(F); int[] rev = buildReverseNodes(F, R, shared); SlowIntersectionFinder slow = new SlowIntersectionFinder(); FastIntersectionFinder fast = new FastIntersectionFinder(); int slowResult = slow.findIntersection(fwd, rev); int fastResult = fast.findIntersection(fwd, rev); check("no-intersection: both return -1 (slow=" + slowResult + " fast=" + fastResult + ")", slowResult == -1 && fastResult == -1); } // --- Test 3: complexity — worst case (no intersection) --- { // No shared nodes → slow does F×R comparisons int F = 200, R = 200; int[] fwd = buildForwardNodes(F); int[] rev = buildReverseNodes(F, R, 0); // no intersection SlowIntersectionFinder slow = new SlowIntersectionFinder(); FastIntersectionFinder fast = new FastIntersectionFinder(); slow.findIntersection(fwd, rev); fast.findIntersection(fwd, rev); long expectedSlowOps = (long) F * R; // scans all F for each of R reverse nodes double ratio = (double) slow.ops / fast.ops; check("worst-case: slow_ops == F*R (" + slow.ops + " == " + expectedSlowOps + ")", slow.ops == expectedSlowOps); check("worst-case: fast_ops == R (" + fast.ops + " == " + R + ")", fast.ops == R); check("worst-case: ratio >= 10x (actual " + String.format("%.1f", ratio) + "x)", ratio >= 10.0); System.out.println(" slow_ops=" + slow.ops + " fast_ops=" + fast.ops + " ratio=" + String.format("%.0f", ratio) + "x (F=" + F + " R=" + R + ")"); } // --- Test 4: complexity — early exit (intersection at start of reverse) --- { // Intersection at first reverse node → slow does F comparisons, fast does 1 int F = 200, R = 200; int[] fwd = buildForwardNodes(F); // Make first reverse node = 0 (first forward node → found immediately in fast, // but slow scans forward until it finds it = position 0 = 1 op) int[] rev = new int[R]; rev[0] = 0; // immediately in forward set for (int i = 1; i < R; i++) rev[i] = F * 2 + i; // rest disjoint SlowIntersectionFinder slow = new SlowIntersectionFinder(); FastIntersectionFinder fast = new FastIntersectionFinder(); int slowResult = slow.findIntersection(fwd, rev); int fastResult = fast.findIntersection(fwd, rev); check("early-exit: both find node 0", slowResult == 0 && fastResult == 0); check("early-exit: slow_ops=1 (found at start of forward)", slow.ops == 1); check("early-exit: fast_ops=1 (hash lookup)", fast.ops == 1); } // --- Test 5: complexity — intersection at END of forward (worst linear scan) --- { // Intersection at forward[F-1] → slow must scan all F nodes before finding match int F = 300, R = 100; int[] fwd = buildForwardNodes(F); // rev[0] = F-1 → found, but only after scanning all F forward nodes (slow) int[] rev = new int[R]; rev[0] = F - 1; for (int i = 1; i < R; i++) rev[i] = F * 2 + i; SlowIntersectionFinder slow = new SlowIntersectionFinder(); FastIntersectionFinder fast = new FastIntersectionFinder(); slow.findIntersection(fwd, rev); fast.findIntersection(fwd, rev); double ratio = (double) slow.ops / fast.ops; check("tail-match: slow_ops == F (scanned all forward, slow=" + slow.ops + ")", slow.ops == F); check("tail-match: fast_ops == 1 (hash, fast=" + fast.ops + ")", fast.ops == 1); check("tail-match: ratio >= F (" + String.format("%.0f", ratio) + "x >= " + F + "x)", ratio >= F); System.out.println(" slow_ops=" + slow.ops + " fast_ops=" + fast.ops + " ratio=" + String.format("%.0f", ratio) + "x (F=" + F + " R=" + R + ")"); } System.out.println("\n" + passCount + "/" + checkCount + " PASS"); } }