wave9b: 455/204 — graphhopper-0001/0002 + valhalla-0001 + OSRM CLEAN
This commit is contained in:
parent
16cc7ecf1d
commit
81bc62b9cb
9 changed files with 811 additions and 5 deletions
228
defects/valhalla/unit/IsSlipLaneAlgorithm.java
Normal file
228
defects/valhalla/unit/IsSlipLaneAlgorithm.java
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue