|
| 1 | +/** |
| 2 | + * This file adapted from JSNetworkX: https://github.com/fkling/JSNetworkX |
| 3 | + * Copyright (C) 2012 Felix Kling <felix.kling@gmx.net> |
| 4 | + * JSNetworkX is distributed with the BSD license |
| 5 | + */ |
| 6 | + |
| 7 | +export function hasPath(G, {source, target}) { |
| 8 | + try { |
| 9 | + shortestPath(G, {source, target}); |
| 10 | + } catch(error) { |
| 11 | + if (error instanceof JSNetworkXNoPath) { |
| 12 | + return false; |
| 13 | + } |
| 14 | + throw error; |
| 15 | + } |
| 16 | + return true; |
| 17 | + } |
| 18 | + |
| 19 | +function shortestPath(G, {source, target, weight}={}) { |
| 20 | + return bidirectionalShortestPath(G, source, target); |
| 21 | +} |
| 22 | + |
| 23 | +function bidirectionalShortestPath(G, source, target) { |
| 24 | + // call helper to do the real work |
| 25 | + var [pred, succ, w] = bidirectionalPredSucc(G, source, target); |
| 26 | + |
| 27 | + // build path from pred+w+succ |
| 28 | + var path = []; |
| 29 | + // from source to w |
| 30 | + while (w != null) { |
| 31 | + path.push(w); |
| 32 | + w = pred.get(w); |
| 33 | + } |
| 34 | + w = succ.get(path[0]); |
| 35 | + path.reverse(); |
| 36 | + // from w to target |
| 37 | + while (w != null) { |
| 38 | + path.push(w); |
| 39 | + w = succ.get(w); |
| 40 | + } |
| 41 | + return path; |
| 42 | +} |
| 43 | + |
| 44 | +function bidirectionalPredSucc(G, source, target) { |
| 45 | + // does BFS from both source and target and meets in the middle |
| 46 | + if (nodesAreEqual(source, target)) { |
| 47 | + return [new Map([[source, null]]), new Map([[target, null]]), source]; |
| 48 | + } |
| 49 | + |
| 50 | + // handle either directed or undirected |
| 51 | + var gpred, gsucc; |
| 52 | + gpred = G.predecessorsIter.bind(G); |
| 53 | + gsucc = G.successorsIter.bind(G); |
| 54 | + |
| 55 | + // predecesssor and successors in search |
| 56 | + var pred = new Map([[source, null]]); |
| 57 | + var succ = new Map([[target, null]]); |
| 58 | + // |
| 59 | + // initialize fringes, start with forward |
| 60 | + var forwardFringe = [source]; |
| 61 | + var reverseFringe = [target]; |
| 62 | + var thisLevel; |
| 63 | + |
| 64 | + /*jshint newcap:false*/ |
| 65 | + while (forwardFringe.length > 0 && reverseFringe.length > 0) { |
| 66 | + if (forwardFringe.length <= reverseFringe.length) { |
| 67 | + thisLevel = forwardFringe; |
| 68 | + forwardFringe = []; |
| 69 | + for (let v of thisLevel) { |
| 70 | + for (let w of gsucc(v)) { |
| 71 | + if (!pred.has(w)) { |
| 72 | + forwardFringe.push(w); |
| 73 | + pred.set(w, v); |
| 74 | + } |
| 75 | + if (succ.has(w)) { |
| 76 | + return [pred, succ, w]; // found path |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + else { |
| 82 | + thisLevel = reverseFringe; |
| 83 | + reverseFringe = []; |
| 84 | + for (let v of thisLevel) { |
| 85 | + for (let w of gpred(v)) { |
| 86 | + if (!succ.has(w)) { |
| 87 | + reverseFringe.push(w); |
| 88 | + succ.set(w, v); |
| 89 | + } |
| 90 | + if (pred.has(w)) { |
| 91 | + return [pred, succ, w]; // found path |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | + throw new JSNetworkXNoPath(sprintf( |
| 98 | + 'No path between `%j` and `%j`.', |
| 99 | + source, |
| 100 | + target |
| 101 | + )); |
| 102 | +} |
| 103 | + |
| 104 | +function topologicalSort(G, optNbunch) { |
| 105 | + // nonrecursive version |
| 106 | + var seen = new Set(); |
| 107 | + var orderExplored = []; // provide order and |
| 108 | + // fast search without more general priorityDictionary |
| 109 | + var explored = new Set(); |
| 110 | + |
| 111 | + if (optNbunch == null) { |
| 112 | + optNbunch = G.nodesIter(); |
| 113 | + } |
| 114 | + |
| 115 | + optNbunch.forEach(function(v) { // process all vertices in G |
| 116 | + if (explored.has(v)) { |
| 117 | + return; // continue |
| 118 | + } |
| 119 | + |
| 120 | + var fringe = [v]; // nodes yet to look at |
| 121 | + while (fringe.length > 0) { |
| 122 | + var w = fringe[fringe.length - 1]; // depth first search |
| 123 | + if (explored.has(w)) { // already looked down this branch |
| 124 | + fringe.pop(); |
| 125 | + continue; |
| 126 | + } |
| 127 | + seen.add(w); // mark as seen |
| 128 | + // Check successors for cycles for new nodes |
| 129 | + var newNodes = []; |
| 130 | + /*eslint-disable no-loop-func*/ |
| 131 | + G.get(w).forEach(function(_, n) { |
| 132 | + if (!explored.has(n)) { |
| 133 | + if (seen.has(n)) { // CYCLE !! |
| 134 | + throw new JSNetworkXUnfeasible('Graph contains a cycle.'); |
| 135 | + } |
| 136 | + newNodes.push(n); |
| 137 | + } |
| 138 | + }); |
| 139 | + /*eslint-enable no-loop-func*/ |
| 140 | + if (newNodes.length > 0) { // add new nodes to fringe |
| 141 | + fringe.push.apply(fringe, newNodes); |
| 142 | + } |
| 143 | + else { |
| 144 | + explored.add(w); |
| 145 | + orderExplored.unshift(w); |
| 146 | + } |
| 147 | + } |
| 148 | + }); |
| 149 | + |
| 150 | + return orderExplored; |
| 151 | +} |
| 152 | + |
| 153 | +export function isDirectedAcyclicGraph(G) { |
| 154 | + try { |
| 155 | + topologicalSort(G); |
| 156 | + return true; |
| 157 | + } |
| 158 | + catch(ex) { |
| 159 | + if (ex instanceof JSNetworkXUnfeasible) { |
| 160 | + return false; |
| 161 | + } |
| 162 | + throw ex; |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +export class DiGraph { |
| 167 | + |
| 168 | + constructor() { |
| 169 | + this.graph = {}; // dictionary for graph attributes |
| 170 | + this.node = new Map(); // dictionary for node attributes |
| 171 | + // We store two adjacency lists: |
| 172 | + // the predecessors of node n are stored in the dict self.pred |
| 173 | + // the successors of node n are stored in the dict self.succ=self.adj |
| 174 | + this.adj = new Map(); // empty adjacency dictionary |
| 175 | + this.pred = new Map(); // predecessor |
| 176 | + this.succ = this.adj; // successor |
| 177 | + |
| 178 | + this.edge = this.adj; |
| 179 | + } |
| 180 | + |
| 181 | + addNode(n) { |
| 182 | + if(!this.succ.has(n)) { |
| 183 | + this.succ.set(n, new Map()); |
| 184 | + this.pred.set(n, new Map()); |
| 185 | + this.node.set(n); |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + addEdge(u, v) { |
| 190 | + // add nodes |
| 191 | + if (!this.succ.has(u)) { |
| 192 | + this.succ.set(u, new Map()); |
| 193 | + this.pred.set(u, new Map()); |
| 194 | + this.node.set(u, {}); |
| 195 | + } |
| 196 | + |
| 197 | + if (!this.succ.has(v)) { |
| 198 | + this.succ.set(v, new Map()); |
| 199 | + this.pred.set(v, new Map()); |
| 200 | + this.node.set(v, {}); |
| 201 | + } |
| 202 | + |
| 203 | + // add the edge |
| 204 | + var datadict = this.adj.get(u).get(v) || {}; |
| 205 | + this.succ.get(u).set(v, datadict); |
| 206 | + this.pred.get(v).set(u, datadict); |
| 207 | + } |
| 208 | + |
| 209 | + nodes(optData=false) { |
| 210 | + return Array.from(optData ? this.node.entries() : this.node.keys()); |
| 211 | + } |
| 212 | + |
| 213 | + edges(optNbunch, optData=false) { |
| 214 | + return Array.from(this.edgesIter(optNbunch, optData)); |
| 215 | + } |
| 216 | + |
| 217 | + nodesIter(optData=false) { |
| 218 | + if (optData) { |
| 219 | + return toIterator(this.node); |
| 220 | + } |
| 221 | + return this.node.keys(); |
| 222 | + } |
| 223 | + |
| 224 | + reverse(optCopy=true) { |
| 225 | + var H; |
| 226 | + if(optCopy) { |
| 227 | + H = new this.constructor(null, {name: 'Reverse of (' + this.name + ')'}); |
| 228 | + H.addNodesFrom(this); |
| 229 | + H.addEdgesFrom(mapIterator( |
| 230 | + this.edgesIter(null, true), |
| 231 | + edge => tuple3c(edge[1], edge[0], deepcopy(edge[2]), edge) |
| 232 | + )); |
| 233 | + H.graph = deepcopy(this.graph); |
| 234 | + H.node = deepcopy(this.node); |
| 235 | + } |
| 236 | + else { |
| 237 | + var thisPred = this.pred; |
| 238 | + var thisSucc = this.succ; |
| 239 | + |
| 240 | + this.succ = thisPred; |
| 241 | + this.pred = thisSucc; |
| 242 | + this.adj = this.succ; |
| 243 | + H = this; |
| 244 | + } |
| 245 | + return H; |
| 246 | + } |
| 247 | + |
| 248 | + successorsIter(n) { |
| 249 | + var nbrs = this.succ.get(n); |
| 250 | + if (nbrs !== undefined) { |
| 251 | + return nbrs.keys(); |
| 252 | + } |
| 253 | + throw new JSNetworkXError( |
| 254 | + sprintf('The node "%j" is not in the digraph.', n) |
| 255 | + ); |
| 256 | + } |
| 257 | + |
| 258 | + predecessorsIter(n) { |
| 259 | + var nbrs = this.pred.get(n); |
| 260 | + if (nbrs !== undefined) { |
| 261 | + return nbrs.keys(); |
| 262 | + } |
| 263 | + throw new JSNetworkXError( |
| 264 | + sprintf('The node "%j" is not in the digraph.', n) |
| 265 | + ); |
| 266 | + } |
| 267 | + |
| 268 | + successors(n) { |
| 269 | + return Array.from(this.successorsIter(n)); |
| 270 | + } |
| 271 | + |
| 272 | + predecessors(n) { |
| 273 | + return Array.from(this.predecessorsIter(n)); |
| 274 | + } |
| 275 | + |
| 276 | +} |
| 277 | + |
| 278 | +class JSNetworkXException { |
| 279 | + constructor(message) { |
| 280 | + this.name = 'JSNetworkXException'; |
| 281 | + this.message = message; |
| 282 | + } |
| 283 | +} |
| 284 | + |
| 285 | +class JSNetworkXAlgorithmError extends JSNetworkXException { |
| 286 | + constructor(message) { |
| 287 | + super(message); |
| 288 | + this.name = 'JSNetworkXAlgorithmError'; |
| 289 | + } |
| 290 | +} |
| 291 | + |
| 292 | +class JSNetworkXError extends JSNetworkXException { |
| 293 | + constructor(message) { |
| 294 | + super(message); |
| 295 | + this.name = 'JSNetworkXError'; |
| 296 | + } |
| 297 | +} |
| 298 | + |
| 299 | +class JSNetworkXUnfeasible extends JSNetworkXAlgorithmError { |
| 300 | + constructor(message) { |
| 301 | + super(message); |
| 302 | + this.name = 'JSNetworkXUnfeasible'; |
| 303 | + } |
| 304 | +} |
| 305 | + |
| 306 | +class JSNetworkXNoPath extends JSNetworkXUnfeasible { |
| 307 | + constructor(message) { |
| 308 | + super(message); |
| 309 | + this.name = 'JSNetworkXNoPath'; |
| 310 | + } |
| 311 | +} |
0 commit comments