-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbidirectional.js
executable file
·69 lines (63 loc) · 2.61 KB
/
bidirectional.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env node
// Node: { name: /* city name */, parent: /*points to another node or null*/, cost: /* number - cost from an initial position */}
// Edge: { nodes: [city_name1, city_name2], cost: number }
// Problem:
// { "initial_states": [cityname_1, ...] // Possible starting points
// , "terminal_states": [cityname_1, ...] // Possible finishing points
// , "edges": [ edge1, edge2, ... ] // See above for the form of an edge
// }
function ancestry(node){
var route = [];
var terminus = node;
while(node){ route.push(node.name); node = node.parent; }
return {route: route.reverse(), cost:terminus.cost};
}
function setPath(obj,path,val){ // setPath({}, ['a',5,'b'],99) => { a: { '5': { b: 99 } } }
path.reduce((obj, node, i) => obj=obj[node]=i===path.length-1?val:obj[node]?obj[node]:{}, obj);
return obj;
}
function set(d,k,v){
d[k] = v;
return d;
}
function bidirectional_search(problem){
var visited = [problem.initial_states, problem.terminal_states]
.map((states) => new Set(states));
// Queues:
var boundaries = [problem.initial_states, problem.terminal_states]
.map((states) => states.map((name) => {return {name:name,parent:null,cost:0};}));
// For quick lookup:
var termini = [problem.initial_states, problem.terminal_states]
.map((states) => states.reduce((d,state) => d.add(state), new Set()));
// Make an index of edges by node so that I can refer to them thus: adjacent[city1][city2] == edge;
var adjacent = problem.edges.reduce
((adjacent, edge) => edge.nodes.reduce
((adjacent, node, i) => setPath(adjacent,[edge.nodes[i], edge.nodes[i^1]], edge)
, adjacent
)
, {});
// Search away:
while(Math.min(...boundaries.map((x) => x.length)) > 0) {
var shorter = Number(boundaries[0].length > boundaries[1].length);
var node = boundaries[shorter].shift();
termini[shorter].delete(node.name);
if (termini[shorter^1].has(node.name)) {
var forward_leg = ancestry(node);
var reverse_leg = ancestry(boundaries[shorter^1].find((shadow) => shadow.name === node.name));
return { cost: forward_leg.cost + reverse_leg.cost
, route: forward_leg.route.concat(reverse_leg.route.reverse().slice(1))
};
}
visited[shorter].add(node.name);
Object.keys(adjacent[node.name])
.filter((adj_node_name) => !visited[shorter].has(adj_node_name))
.forEach((adj_node_name) => {
boundaries[shorter].push({name:adj_node_name, parent:node, cost:node.cost + adjacent[node.name][adj_node_name].cost});
termini[shorter].add(adj_node_name);
});
}
}
module.exports = bidirectional_search;
if (!module.parent){
console.log(bidirectional_search(require(process.argv[2] || './data')));
}