(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Graph = f()}})(function(){var define,module,exports;return (function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o ['A', 'B', 'C', 'D'] * * // trimmed * route.path('A', 'D', { trim: true }) // => [B', 'C'] * * // reversed * route.path('A', 'D', { reverse: true }) // => ['D', 'C', 'B', 'A'] * * // include the cost * route.path('A', 'D', { cost: true }) * // => { * // path: [ 'A', 'B', 'C', 'D' ], * // cost: 4 * // } */ path(start, goal, options = {}) { // Don't run when we don't have nodes set if (!this.graph.size) { if (options.cost) return { path: null, cost: 0 }; return null; } const explored = new Set(); const frontier = new Queue(); const previous = new Map(); let path = []; let totalCost = 0; let avoid = []; if (options.avoid) avoid = [].concat(options.avoid); if (avoid.includes(start)) { throw new Error(`Starting node (${start}) cannot be avoided`); } else if (avoid.includes(goal)) { throw new Error(`Ending node (${goal}) cannot be avoided`); } // Add the starting point to the frontier, it will be the first node visited frontier.set(start, 0); // Run until we have visited every node in the frontier while (!frontier.isEmpty()) { // Get the node in the frontier with the lowest cost (`priority`) const node = frontier.next(); // When the node with the lowest cost in the frontier in our goal node, // we can compute the path and exit the loop if (node.key === goal) { // Set the total cost to the current value totalCost = node.priority; let nodeKey = node.key; while (previous.has(nodeKey)) { path.push(nodeKey); nodeKey = previous.get(nodeKey); } break; } // Add the current node to the explored set explored.add(node.key); // Loop all the neighboring nodes const neighbors = this.graph.get(node.key) || new Map(); neighbors.forEach((nCost, nNode) => { // If we already explored the node, or the node is to be avoided, skip it if (explored.has(nNode) || avoid.includes(nNode)) return null; // If the neighboring node is not yet in the frontier, we add it with // the correct cost if (!frontier.has(nNode)) { previous.set(nNode, node.key); return frontier.set(nNode, node.priority + nCost); } const frontierPriority = frontier.get(nNode).priority; const nodeCost = node.priority + nCost; // Otherwise we only update the cost of this node in the frontier when // it's below what's currently set if (nodeCost < frontierPriority) { previous.set(nNode, node.key); return frontier.set(nNode, nodeCost); } return null; }); } // Return null when no path can be found if (!path.length) { if (options.cost) return { path: null, cost: 0 }; return null; } // From now on, keep in mind that `path` is populated in reverse order, // from destination to origin // Remove the first value (the goal node) if we want a trimmed result if (options.trim) { path.shift(); } else { // Add the origin waypoint at the end of the array path = path.concat([start]); } // Reverse the path if we don't want it reversed, so the result will be // from `start` to `goal` if (!options.reverse) { path = path.reverse(); } // Return an object if we also want the cost if (options.cost) { return { path, cost: totalCost, }; } return path; } /** * @deprecated since version 2.0, use `Graph#path` instead */ shortestPath(...args) { return this.path(...args); } } module.exports = Graph; },{"./PriorityQueue":2,"./removeDeepFromMap":3,"./toDeepMap":4,"./validateDeep":5}],2:[function(require,module,exports){ /** * This very basic implementation of a priority queue is used to select the * next node of the graph to walk to. * * The queue is always sorted to have the least expensive node on top. * Some helper methods are also implemented. * * You should **never** modify the queue directly, but only using the methods * provided by the class. */ class PriorityQueue { /** * Creates a new empty priority queue */ constructor() { // The `keys` set is used to greatly improve the speed at which we can // check the presence of a value in the queue this.keys = new Set(); this.queue = []; } /** * Sort the queue to have the least expensive node to visit on top * * @private */ sort() { this.queue.sort((a, b) => a.priority - b.priority); } /** * Sets a priority for a key in the queue. * Inserts it in the queue if it does not already exists. * * @param {any} key Key to update or insert * @param {number} value Priority of the key * @return {number} Size of the queue */ set(key, value) { const priority = Number(value); if (isNaN(priority)) throw new TypeError('"priority" must be a number'); if (!this.keys.has(key)) { // Insert a new entry if the key is not already in the queue this.keys.add(key); this.queue.push({ key, priority }); } else { // Update the priority of an existing key this.queue.map((element) => { if (element.key === key) { Object.assign(element, { priority }); } return element; }); } this.sort(); return this.queue.length; } /** * The next method is used to dequeue a key: * It removes the first element from the queue and returns it * * @return {object} First priority queue entry */ next() { const element = this.queue.shift(); // Remove the key from the `_keys` set this.keys.delete(element.key); return element; } /** * @return {boolean} `true` when the queue is empty */ isEmpty() { return Boolean(this.queue.length === 0); } /** * Check if the queue has a key in it * * @param {any} key Key to lookup * @return {boolean} */ has(key) { return this.keys.has(key); } /** * Get the element in the queue with the specified key * * @param {any} key Key to lookup * @return {object} */ get(key) { return this.queue.find(element => element.key === key); } } module.exports = PriorityQueue; },{}],3:[function(require,module,exports){ /** * Removes a key and all of its references from a map. * This function has no side-effects as it returns * a brand new map. * * @param {Map} map - Map to remove the key from * @param {string} key - Key to remove from the map * @return {Map} New map without the passed key */ function removeDeepFromMap(map, key) { const newMap = new Map(); for (const [aKey, val] of map) { if (aKey !== key && val instanceof Map) { newMap.set(aKey, removeDeepFromMap(val, key)); } else if (aKey !== key) { newMap.set(aKey, val); } } return newMap; } module.exports = removeDeepFromMap; },{}],4:[function(require,module,exports){ /** * Validates a cost for a node * * @private * @param {number} val - Cost to validate * @return {bool} */ function isValidNode(val) { const cost = Number(val); if (isNaN(cost) || cost <= 0) { return false; } return true; } /** * Creates a deep `Map` from the passed object. * * @param {Object} source - Object to populate the map with * @return {Map} New map with the passed object data */ function toDeepMap(source) { const map = new Map(); const keys = Object.keys(source); keys.forEach((key) => { const val = source[key]; if (val !== null && typeof val === 'object' && !Array.isArray(val)) { return map.set(key, toDeepMap(val)); } if (!isValidNode(val)) { throw new Error(`Could not add node at key "${key}", make sure it's a valid node`, val); } return map.set(key, Number(val)); }); return map; } module.exports = toDeepMap; },{}],5:[function(require,module,exports){ /** * Validate a map to ensure all it's values are either a number or a map * * @param {Map} map - Map to valiadte */ function validateDeep(map) { if (!(map instanceof Map)) { throw new Error(`Invalid graph: Expected Map instead found ${typeof map}`); } map.forEach((value, key) => { if (typeof value === 'object' && value instanceof Map) { validateDeep(value); return; } if (typeof value !== 'number' || value <= 0) { throw new Error(`Values must be numbers greater than 0. Found value ${value} at ${key}`); } }); } module.exports = validateDeep; },{}]},{},[1])(1) });