feat: obvious-dijkstra & attack detection
This commit is contained in:
parent
8c117633c5
commit
4fa95ff78a
|
@ -5,14 +5,12 @@
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="3c7078e7-6f30-4d92-9696-11496f9e6dff" name="Changes" comment="">
|
<list default="true" id="3c7078e7-6f30-4d92-9696-11496f9e6dff" name="Changes" comment="">
|
||||||
<change afterPath="$PROJECT_DIR$/PublicProperty.js" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/tools/Misc.js" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/tools/Debug.js" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/tools/PathBench.js" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/tools/ShortestPath.js" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/api/click.js" beforeDir="false" afterPath="$PROJECT_DIR$/api/click.js" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/api/click.js" beforeDir="false" afterPath="$PROJECT_DIR$/api/click.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/api/handshake.js" beforeDir="false" afterPath="$PROJECT_DIR$/api/handshake.js" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/tools/Debug.js" beforeDir="false" afterPath="$PROJECT_DIR$/tools/Debug.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Networking.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/Networking.js" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/tools/ShortestPath.js" beforeDir="false" afterPath="$PROJECT_DIR$/tools/ShortestPath.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/UMap.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/UMap.js" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
@ -95,7 +93,7 @@
|
||||||
<workItem from="1702448962541" duration="5620000" />
|
<workItem from="1702448962541" duration="5620000" />
|
||||||
<workItem from="1703419885970" duration="13073000" />
|
<workItem from="1703419885970" duration="13073000" />
|
||||||
<workItem from="1703582457934" duration="210000" />
|
<workItem from="1703582457934" duration="210000" />
|
||||||
<workItem from="1703642799206" duration="3401000" />
|
<workItem from="1703642799206" duration="15845000" />
|
||||||
</task>
|
</task>
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
|
|
93
api/click.js
93
api/click.js
|
@ -1,12 +1,14 @@
|
||||||
import {dijkstra, haversine_distance} from "../tools/ShortestPath";
|
import {dijkstra, haversine_distance, obvious_dijkstra} from "../tools/ShortestPath";
|
||||||
import {sill, sill_unwrap, get_row, noexcept} from "../tools/Debug";
|
import {sill, sill_unwrap, noexcept} from "../tools/Debug";
|
||||||
|
import {get_row} from "../tools/Misc";
|
||||||
|
import benchmark from "../tools/PathBench";
|
||||||
|
|
||||||
const __spa = dijkstra;
|
const __spa = benchmark;
|
||||||
|
|
||||||
function find_nearest_node_id(nodes, point) {
|
function find_nearest_node_id(nodes, point) {
|
||||||
const [lat, lon] = point;
|
const [lat, lon] = point;
|
||||||
let min_distance = 1e100;
|
let min_distance = 1e100;
|
||||||
let res = 0;
|
let res = '';
|
||||||
for (let node_id in nodes) {
|
for (let node_id in nodes) {
|
||||||
const curr_distance = haversine_distance(nodes[node_id], point);
|
const curr_distance = haversine_distance(nodes[node_id], point);
|
||||||
if (curr_distance < min_distance) {
|
if (curr_distance < min_distance) {
|
||||||
|
@ -18,44 +20,71 @@ function find_nearest_node_id(nodes, point) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortest_path = noexcept((nodes, ways, start_point, end_point) => {
|
const shortest_path = noexcept((nodes, ways, start_point, end_point) => {
|
||||||
|
// const clean_nodes = nodes;
|
||||||
const count = {};
|
const count = {};
|
||||||
for(const way_id in ways) {
|
const aff = {};
|
||||||
sill(way_id);
|
const clean_nodes = {};
|
||||||
const [l, n] = get_row(ways, way_id);
|
|
||||||
for(let i = 0; i < n; ++i) {
|
|
||||||
if(count[l[i]]) {
|
|
||||||
++count[l[i]];
|
|
||||||
} else {
|
|
||||||
count[l[i]] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sill(count);
|
|
||||||
const ch_dict = {};
|
|
||||||
for (const way_id in ways) {
|
for (const way_id in ways) {
|
||||||
|
// sill_unwrap(way_id);
|
||||||
|
const st = new Set();
|
||||||
const [l, n] = get_row(ways, way_id);
|
const [l, n] = get_row(ways, way_id);
|
||||||
for (let i = 0; i < n; ++i) {
|
for (let i = 0; i < n; ++i) {
|
||||||
if (ch_dict[l[i]]) {
|
const curr = l[i];
|
||||||
ch_dict[l[i]].push(l[i - 1]);
|
if (st.has(curr)) continue;
|
||||||
|
st.add(curr);
|
||||||
|
clean_nodes[curr] = nodes[curr];
|
||||||
|
if (count[curr]) {
|
||||||
|
++count[curr];
|
||||||
} else {
|
} else {
|
||||||
ch_dict[l[i]] = [l[i - 1]];
|
count[curr] = 1;
|
||||||
}
|
}
|
||||||
if (ch_dict[l[i - 1]]) {
|
if (aff[curr]) {
|
||||||
ch_dict[l[i - 1]].push(l[i]);
|
aff[curr][way_id] = true;
|
||||||
} else {
|
} else {
|
||||||
ch_dict[l[i - 1]] = [l[i]];
|
aff[curr] = {[way_id]: true};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const clean_nodes = {};
|
sill_unwrap(aff);
|
||||||
Object.keys(nodes).forEach((node_id) => {
|
|
||||||
if (ch_dict[node_id]) clean_nodes[node_id] = nodes[node_id];
|
|
||||||
});
|
|
||||||
sill_unwrap(start_point);
|
|
||||||
const actual_start_node_id = find_nearest_node_id(clean_nodes, start_point);
|
const actual_start_node_id = find_nearest_node_id(clean_nodes, start_point);
|
||||||
const actual_end_node_id = find_nearest_node_id(clean_nodes, end_point);
|
const actual_end_node_id = find_nearest_node_id(clean_nodes, end_point);
|
||||||
|
sill_unwrap(typeof (actual_start_node_id));
|
||||||
|
// sill(count);
|
||||||
|
const ch_dict = {};
|
||||||
|
let f = 1;
|
||||||
|
for (const t in ways) {
|
||||||
|
if (t === ways[aff[actual_start_node_id]]) sill('yes');
|
||||||
|
const [l, n] = get_row(ways, t);
|
||||||
|
let prev = '';
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
const curr = l[i].toString();
|
||||||
|
// if(true) {
|
||||||
|
if (count[curr] > 1 || curr === actual_end_node_id || curr === actual_start_node_id) {
|
||||||
|
if (curr === actual_start_node_id) sill(curr === actual_start_node_id);
|
||||||
|
if (prev !== '') {
|
||||||
|
if (ch_dict[curr]) {
|
||||||
|
ch_dict[curr].push(prev);
|
||||||
|
} else {
|
||||||
|
ch_dict[curr] = [prev];
|
||||||
|
}
|
||||||
|
if (ch_dict[prev]) {
|
||||||
|
ch_dict[prev].push(curr);
|
||||||
|
} else {
|
||||||
|
ch_dict[prev] = [curr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const clean_nodes = {};
|
||||||
|
// Object.keys(nodes).forEach((node_id) => {
|
||||||
|
// if (ch_dict[node_id]) clean_nodes[node_id] = nodes[node_id];
|
||||||
|
// });
|
||||||
|
sill(`start distance: ${haversine_distance(nodes[actual_start_node_id], start_point)}`);
|
||||||
|
sill(`dest distance: ${haversine_distance(nodes[actual_end_node_id], end_point)}`);
|
||||||
sill("calling __spa...");
|
sill("calling __spa...");
|
||||||
const seq = __spa(clean_nodes, ch_dict, actual_start_node_id, actual_end_node_id);
|
const seq = __spa(nodes, clean_nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id);
|
||||||
const res = [end_point];
|
const res = [end_point];
|
||||||
seq.forEach((node_id) => {
|
seq.forEach((node_id) => {
|
||||||
if (clean_nodes[node_id]) res.push(clean_nodes[node_id]);
|
if (clean_nodes[node_id]) res.push(clean_nodes[node_id]);
|
||||||
|
@ -71,13 +100,17 @@ export default function handler(req, res) {
|
||||||
lonRange = pts.map((row) => row[1]);
|
lonRange = pts.map((row) => row[1]);
|
||||||
const minlon = Math.min(...lonRange) - 0.01, minlat = Math.min(...latRange) - 0.01,
|
const minlon = Math.min(...lonRange) - 0.01, minlat = Math.min(...latRange) - 0.01,
|
||||||
maxlon = Math.max(...lonRange) + 0.01, maxlat = Math.max(...latRange) + 0.01;
|
maxlon = Math.max(...lonRange) + 0.01, maxlat = Math.max(...latRange) + 0.01;
|
||||||
|
if (haversine_distance([minlat, minlon], [maxlat, maxlon]) > 100) {
|
||||||
|
res.status(500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const request_uri = `https://www.overpass-api.de/api/interpreter?data=[out:json];way[highway](${minlat},${minlon},${maxlat},${maxlon});(._;>;);out body;`;
|
const request_uri = `https://www.overpass-api.de/api/interpreter?data=[out:json];way[highway](${minlat},${minlon},${maxlat},${maxlon});(._;>;);out body;`;
|
||||||
sill(`Requesting ${request_uri}`);
|
sill(`Requesting ${request_uri}`);
|
||||||
const fetch_debug_response = fetch(request_uri).then((response) => {
|
const fetch_debug_response = fetch(request_uri).then((response) => {
|
||||||
return response.json();
|
return response.json();
|
||||||
});
|
});
|
||||||
fetch_debug_response.then((debug_response) => {
|
fetch_debug_response.then((debug_response) => {
|
||||||
sill(debug_response);
|
// sill(debug_response);
|
||||||
let ps = {};
|
let ps = {};
|
||||||
let ws = {};
|
let ws = {};
|
||||||
debug_response.elements.forEach((it) => {
|
debug_response.elements.forEach((it) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// no dependencies
|
// no dependencies
|
||||||
import {__DEBUG__} from "../PublicProperty";
|
import {__DEBUG__} from "../PublicProperty";
|
||||||
|
|
||||||
export function noexcept(f) {
|
export function noexcept(_f) {
|
||||||
/**
|
/**
|
||||||
* No exceptions.
|
* No exceptions.
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
|
@ -9,7 +9,7 @@ export function noexcept(f) {
|
||||||
*/
|
*/
|
||||||
function _wrap() {
|
function _wrap() {
|
||||||
try {
|
try {
|
||||||
return f.apply(this, arguments);
|
return _f.apply(this, arguments);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.debug(e);
|
console.debug(e);
|
||||||
}
|
}
|
||||||
|
@ -17,17 +17,12 @@ export function noexcept(f) {
|
||||||
return _wrap;
|
return _wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sill = noexcept((x) => {
|
export const sill = noexcept((_x) => {
|
||||||
if(__DEBUG__) {
|
if(__DEBUG__) {
|
||||||
console.log(x);
|
console.log(_x);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sill_unwrap = noexcept((x) => {
|
export const sill_unwrap = noexcept((_x) => {
|
||||||
sill(JSON.stringify(x));
|
sill(JSON.stringify(_x));
|
||||||
});
|
|
||||||
|
|
||||||
export const get_row = noexcept((a,i) => {
|
|
||||||
const row = a[i];
|
|
||||||
return [row, row.length];
|
|
||||||
});
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
import {noexcept, sill, sill_unwrap} from "./Debug";
|
||||||
|
|
||||||
|
export const get_row = noexcept((_a,_i) => {
|
||||||
|
const row = _a[_i];
|
||||||
|
return [row, row.length];
|
||||||
|
});
|
||||||
|
|
||||||
|
export const find_common = noexcept((_iterable_1, _iterable_2) => {
|
||||||
|
for(const [k,_] of Object.entries(_iterable_2)) {
|
||||||
|
if (_iterable_1[k] === true) {
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,21 @@
|
||||||
|
import * as SP from "./ShortestPath";
|
||||||
|
import {sill} from "./Debug";
|
||||||
|
import {get_row} from "./Misc";
|
||||||
|
|
||||||
|
export default function benchmark(nodes, clean_nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id) {
|
||||||
|
sill(`==========PathBench==========`);
|
||||||
|
let start_time, end_time;
|
||||||
|
let res;
|
||||||
|
//benchmark Obvious-Dijkstra
|
||||||
|
start_time = performance.now();
|
||||||
|
res = SP.obvious_dijkstra(clean_nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id);
|
||||||
|
end_time = performance.now();
|
||||||
|
sill(`Obvious-Dijkstra run-time: ${end_time - start_time} ms`);
|
||||||
|
// benchmark Dijkstra
|
||||||
|
start_time = performance.now();
|
||||||
|
res = SP.dijkstra(nodes, ways, ch_dict, count, aff, actual_start_node_id, actual_end_node_id);
|
||||||
|
end_time = performance.now();
|
||||||
|
sill(`Dijkstra run-time: ${end_time - start_time} ms`);
|
||||||
|
sill(`==============================`);
|
||||||
|
return res;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import {MinPriorityQueue} from "@datastructures-js/priority-queue";
|
import {MinPriorityQueue} from "@datastructures-js/priority-queue";
|
||||||
import {sill, noexcept} from "./Debug";
|
import {sill, noexcept, sill_unwrap} from "./Debug";
|
||||||
|
import {find_common} from "./Misc";
|
||||||
|
|
||||||
function __haversine_distance(p1, p2) {
|
function __haversine_distance(p1, p2) {
|
||||||
const toRadians = (degrees) => {
|
const toRadians = (degrees) => {
|
||||||
|
@ -22,17 +23,20 @@ function __haversine_distance(p1, p2) {
|
||||||
/**
|
/**
|
||||||
* Use Dijkstra algorithm to find the shortest path.
|
* Use Dijkstra algorithm to find the shortest path.
|
||||||
* @param nodes node index list
|
* @param nodes node index list
|
||||||
|
* @param ways unused in this implementation
|
||||||
* @param ch adjacent table
|
* @param ch adjacent table
|
||||||
|
* @param count unused in this implementation
|
||||||
|
* @param aff unused in this implementation
|
||||||
* @param u start node id
|
* @param u start node id
|
||||||
* @param p destination node id
|
* @param p destination node id
|
||||||
*/
|
*/
|
||||||
function __dijkstra(nodes, ch, u, p) {
|
function __dijkstra(nodes, ways, ch, count, aff, u, p) {
|
||||||
sill(`node count: ${Object.keys(nodes).length}`);
|
// sill(`node count: ${Object.keys(nodes).length}`);
|
||||||
const weight_cache = {};
|
const weight_cache = {};
|
||||||
const get_weight = (n1, n2) => {
|
const get_weight = (n1, n2) => {
|
||||||
const tup = [n1, n2];
|
const tup = [n1, n2];
|
||||||
if (weight_cache[tup]) {
|
if (weight_cache[tup]) {
|
||||||
return weight_cache[[n1, n2]];
|
return weight_cache[tup];
|
||||||
}
|
}
|
||||||
weight_cache[tup] = __haversine_distance(nodes[n1], nodes[n2]);
|
weight_cache[tup] = __haversine_distance(nodes[n1], nodes[n2]);
|
||||||
return weight_cache[tup];
|
return weight_cache[tup];
|
||||||
|
@ -46,6 +50,9 @@ function __dijkstra(nodes, ch, u, p) {
|
||||||
while (!pq.isEmpty()) {
|
while (!pq.isEmpty()) {
|
||||||
const [d, v] = pq.pop();
|
const [d, v] = pq.pop();
|
||||||
if (vis.has(v) || !ch[v]) continue;
|
if (vis.has(v) || !ch[v]) continue;
|
||||||
|
if (v === p) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
vis.add(v);
|
vis.add(v);
|
||||||
const t = ch[v].length;
|
const t = ch[v].length;
|
||||||
for (let j = 0; j < t; ++j) {
|
for (let j = 0; j < t; ++j) {
|
||||||
|
@ -71,9 +78,120 @@ function __dijkstra(nodes, ch, u, p) {
|
||||||
vis.add(curr);
|
vis.add(curr);
|
||||||
res.push(curr);
|
res.push(curr);
|
||||||
}
|
}
|
||||||
sill("finished Dijkstra.");
|
// sill("finished Dijkstra.");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Dijkstra algorithm with Obvious optimization to find the shortest path.
|
||||||
|
* @param nodes node index list
|
||||||
|
* @param ways node list for each way
|
||||||
|
* @param ch adjacent table
|
||||||
|
* @param count determine isolated nodes
|
||||||
|
* @param aff affiliation of isolated nodes
|
||||||
|
* @param u start node id
|
||||||
|
* @param p destination node id
|
||||||
|
*/
|
||||||
|
function __obvious_dijkstra(nodes, ways, ch, count, aff, u, p) {
|
||||||
|
// sill(`node count: ${Object.keys(nodes).length}`);
|
||||||
|
const weight_cache = {};
|
||||||
|
const get_weight_along = (way, n1, n2) => {
|
||||||
|
const tup = [n1, n2];
|
||||||
|
if (weight_cache[tup]) {
|
||||||
|
return weight_cache[tup];
|
||||||
|
}
|
||||||
|
let res = 0;
|
||||||
|
const n = way.length;
|
||||||
|
let prev = '';
|
||||||
|
let state = 0;
|
||||||
|
for (let i = 0; i < n; ++i) {
|
||||||
|
const curr = way[i].toString();
|
||||||
|
if (curr === n1 || curr === n2) {
|
||||||
|
if (state) {
|
||||||
|
res += (__haversine_distance(nodes[prev], nodes[curr]));
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
state = 1;
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
} else if (state) {
|
||||||
|
res += (__haversine_distance(nodes[prev], nodes[curr]));
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
weight_cache[tup] = res;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const dis = {};
|
||||||
|
const fa = {};
|
||||||
|
const vis = new Map();
|
||||||
|
const pq = new MinPriorityQueue();
|
||||||
|
dis[u] = 0;
|
||||||
|
pq.push([0, u]);
|
||||||
|
while (!pq.isEmpty()) {
|
||||||
|
const [d, v] = pq.pop();
|
||||||
|
if (vis.has(v) || !ch[v]) {
|
||||||
|
// sill(!ch[v]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (v === p) {
|
||||||
|
// sill('break');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// sill('loop');
|
||||||
|
vis.set(v,true);
|
||||||
|
dis[v] = d;
|
||||||
|
const t = ch[v].length;
|
||||||
|
for (let j = 0; j < t; ++j) {
|
||||||
|
const c = ch[v][j];
|
||||||
|
if (!nodes[c]) continue;
|
||||||
|
const way_id = find_common(aff[c], aff[v]);
|
||||||
|
if (!way_id) sill("FATAL: NO COMMON WAY");
|
||||||
|
const way = ways[way_id];
|
||||||
|
const w = get_weight_along(way, v, c);
|
||||||
|
// sill(w);
|
||||||
|
if (w > 0 && (!dis[c] || d + w < dis[c])) {
|
||||||
|
dis[c] = d + w;
|
||||||
|
pq.push([dis[c], c]);
|
||||||
|
const v_oc = way.indexOf(parseInt(v));
|
||||||
|
const c_oc = way.indexOf(parseInt(c));
|
||||||
|
let prev = c;
|
||||||
|
if (v_oc < c_oc) {
|
||||||
|
for (let _p = c_oc - 1; _p >= v_oc; --_p) {
|
||||||
|
const node = way[_p].toString();
|
||||||
|
fa[prev] = node;
|
||||||
|
if (vis.has(node)) break;
|
||||||
|
prev = node;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let _p = c_oc + 1; _p <= v_oc; ++_p) {
|
||||||
|
const node = way[_p].toString();
|
||||||
|
fa[prev] = node;
|
||||||
|
if (vis.has(node)) break;
|
||||||
|
prev = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sill_unwrap(fa);
|
||||||
|
let curr = p;
|
||||||
|
const res = [p];
|
||||||
|
vis.clear();
|
||||||
|
while (fa[curr]) {
|
||||||
|
curr = fa[curr].toString();
|
||||||
|
if (vis.has(curr)) {
|
||||||
|
sill(`Cycle at ${curr}`);
|
||||||
|
// sill(res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vis.set(curr,true);
|
||||||
|
res.push(curr);
|
||||||
|
}
|
||||||
|
// sill("finished Obvious Dijkstra.");
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const haversine_distance = noexcept(__haversine_distance);
|
export const haversine_distance = noexcept(__haversine_distance);
|
||||||
export const dijkstra = noexcept(__dijkstra);
|
export const dijkstra = noexcept(__dijkstra);
|
||||||
|
export const obvious_dijkstra = noexcept(__obvious_dijkstra);
|
Loading…
Reference in New Issue