java-topology/defects/graphhopper/unit/AlternativeRouteCHNodesContainsAlgorithm.java

255 lines
9.7 KiB
Java
Raw 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 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<Integer> 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");
}
}