java-topology/defects/valhalla/unit/IsSlipLaneAlgorithm.java

228 lines
9.1 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Integer> 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");
}
}