Tihs module is an implementation of the Dijkstra's Algorithm to compute the shortest path between a starting and ending node ina graph
We provide one class Node to create a node, that connect to other nodes and which have the compute method to compute the shortest path from the starting node till the ending node.
You load it like this
var Node = require( 'shortest-way' ).Node;The constructor takes one parameter as an object:
Node( object )objectis an object with 3 propertiesidis the mandatory unique identifier of the node.offsetis an offset to add for each next node added. See the methodaddNext. It is optional.
For example :
var Node = require( 'shortest-way' ).Node;
var start = new Node( { id:'start' } );
start.raw = { cityName: 'Paris'};The node class has a method to add a next node. Hence you build a directional relation between 2 nodes.
node.addNode( nextNode, weight )This method takes 2 parameters:
nextNodeis the next node to add to this nodeweightis the weight of the relation between this node and the next node. It is- a positive number and its detault value is
0. You can concider- it as the distance between 2 nodes
- or the time it takes between 2 nodes.
An error is raised if
- the next node is already was already added to this node
- the
weightis negative
Example:
var Node = require( 'shortest-way' ).Node;
// create nodes
var startNode = new Node( { id: 'start' } );
var middleNode = new Node( { id: 'middle' } );
var endNode = new Node( { id: 'end' } );
// add relation
startNode.addNext( middleNode, 1 );
middleNode.addNext( endNode, 1 );Non-directional relations between two nodes can be set using connectWith:
node.connectWith( anotherNode, weight );Once all nodes are created and linked together with the method addNext, you can call the compute method on the starting node.
var path = startNode.compute( endNode, allNodes )The arguments are :
endNodeis the destination for the shortest path you want to computeallNodescan be- an
arraycontaining all the nodes of the graph - an
objectcontaining all nodes of the graph.
- an
var startNode = new Node( { id: 'start' } );
var middleNode = new Node( { id: 'middle' } );
var endNode = new Node( { id: 'end' } );
// all nodes stored into an array
var allNodes = [ statNode, middleNode, endNode ];
var result = startNode.compute( endNode, allNodes );
// OR all nodes stored into an object
allNodes = {
'start': startNode,
'middle': middleNode,
'end': endNode
};
var result = startNode.compute( endNode, allNodes );Both format for allNodes are supported. If allNodes is an array, then it will be converted once into an object before computing the shortest path because it if faster to compute with an object containing all nodes.
Both arguments are required : endNode and allNodes.
The result of the compute method is :
[
{
id: 'A',
offset: 0,
value: 0,
next: { B: [Object], C: [Object], E: [Object] },
toString: [Function]
},
{
id: 'C',
offset: 0,
value: 217,
next: { G: [Object], H: [Object] },
toString: [Function],
prev:
{
id: 'A',
offset: 0,
value: 0,
next: [Object],
toString: [Function] }
},
{
id: 'H',
offset: 0,
value: 320,
next: { D: [Object], J: [Object] },
toString: [Function],
prev:
{
id: 'C',
offset: 0,
value: 217,
next: [Object],
toString: [Function],
prev: [Object] }
},
{
id: 'J',
offset: 0,
value: 487,
next: {},
toString: [Function],
prev:
{
id: 'H',
offset: 0,
value: 320,
next: [Object],
toString: [Function],
prev: [Object] }
}
]Each element of the array is a node enhanced with some value.
- id
,offset` - are values given to the node when its constructor was called (
new Node( {id:'', offset:0 } )). nextwas populated by the methodaddNext. This object contains all next node of this node.toStringa function to nicely display a nodeprevwas created while computing the shortest path. It is a reference with the previous nearest node.
If there is no path between nodes, empty array will be returned.
Calling JSON.stringify( results ) will lead to an error TypeError: Converting circular structure to JSON. To avoid this, you can remove the previous and next node for each element of the array.
results.forEach( function ( node ) {
delete node.next;
delete node.prev;
} );But if you want to keep the relation between nodes, you can do has follow to keep node.id in the place of next and prev.
results.forEach( function ( node ) {
node.next = Object.keys( node.next );
node.prev = node.prev ? node.prev.id : undefined;
} );Here is the graph we want to give a try from Wikipedia
var Node = require( 'shortest-way' ).Node;
var a = new Node( { id: 'A' }, true );
var b = new Node( { id: 'B' } );
var c = new Node( { id: 'C' } );
var d = new Node( { id: 'D' } );
var e = new Node( { id: 'E' } );
var f = new Node( { id: 'F' } );
var g = new Node( { id: 'G' } );
var h = new Node( { id: 'H' } );
var i = new Node( { id: 'I' } );
var j = new Node( { id: 'J' } );
a.addNext( b, 85 );
a.addNext( c, 217 );
a.addNext( e, 173 );
b.addNext( f, 80 );
c.addNext( g, 186 );
c.addNext( h, 103 );
h.addNext( d, 183 );
f.addNext( i, 250 );
i.addNext( j, 84 );
h.addNext( j, 167 );
e.addNext( j, 502 );
// call
var allNodes = [ a, b, c, d, e, f, g, h, i, j ];
var results = a.compute( j, allNodes );
var path = results.map( function ( node ) {
return node.id
} ).join( ', ' );
console.log( path );will output :
A, C, H, JUntill the version 1.1.1, you could only add one weight between 2 nodes with the method addNext.
Example that don't fail:
var start = new Node( {id:'start'} );
var end = new Node( {id:'end'} );
start.addNext( end, 10 );However, you may know another route from start node to end node. Maybe a faster route. That example that fail:
var start = new Node( {id:'start'} );
var end = new Node( {id:'end'} );
start.addNext( end, 10 );
start.addNext( end, 5 ); // Exception thrownIf you want to setup multiple routes between 2 nodes, we call it transition.
You must use the method addTransition( targetNode, weight, [options] ). This method will create a next node (T) of this node with a weight of 0 and then will setup the next node of T node to the targetNode node with a weight of weight.
The example bellow wont fail
var startNode = new Node( { id: 'start' } );
var endNode = new Node( { id: 'end' } );
var allNodes = [ startNode, endNode ];
allNodes.push( startNode.addTransition( endNode, 1 ) );
allNodes.push( startNode.addTransition( endNode, 2 ) );
allNodes.push( startNode.addTransition( endNode, 3 ) );The result will be:
[ { id: 'start',
offset: 0,
value: 0,
next:
{ 'transition:start>1>end': [Object],
'transition:start>2>end': [Object],
'transition:start>3>end': [Object] },
toString: [Function] },
{ id: 'transition:start>1>end',
offset: 0,
value: 0,
next: { end: [Object] },
toString: [Function],
prev:
{ id: 'start',
offset: 0,
value: 0,
next: [Object],
toString: [Function] } },
{ id: 'end',
offset: 0,
value: 1,
next: {},
toString: [Function],
prev:
{ id: 'transition:start>1>end',
offset: 0,
value: 0,
next: [Object],
toString: [Function],
prev: [Object] } } ]The transition node id is build with the name 'transition:' + start_node.id + '>' + link weight + '>' + end_node.id.
The options opional parameter is an object that can have multiple properties :
- id the transition id
- prefixId the transition prefix (default is 'transition'). Value ignored if id property setup.
- startWeight the weight of the link between the startNode and the transition node.
