Skip to content

Commit

Permalink
Add 'avoid' option and functionality to path function
Browse files Browse the repository at this point in the history
  • Loading branch information
Nate Dobbins authored and albertorestifo committed May 3, 2017
1 parent c9f5d5f commit 29b9924
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,12 @@ route.removeNode('c');
- `String start`: Name of the starting node
- `String goal`: Name of out goal node
- `Object options` _optional_: Addittional options:
- `Boolean trim`, deafult `false`: If set to true, the result won't include the start and goal nodes
- `Boolean trim`, default `false`: If set to true, the result won't include the start and goal nodes
- `Boolean reverse`, default `false`: If set to true, the result will be in reverse order, from goal to start
- `Boolean cost`, default `false`: If set to true, an object will be returned with the following keys:
- `Array path`: Computed path (subject to other options)
- `Number cost`: Total cost for the found path
- `Array avoid`, default `[]`: Nodes to be avoided

#### Returns

Expand Down
13 changes: 11 additions & 2 deletions libs/Graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ class Graph {
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);

Expand Down Expand Up @@ -213,8 +222,8 @@ class Graph {
// Loop all the neighboring nodes
const neighbors = this.graph.get(node.key) || new Map();
neighbors.forEach((nCost, nNode) => {
// If we already explored the node, skip it
if (explored.has(nNode)) return null;
// 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
Expand Down
56 changes: 56 additions & 0 deletions test/Graph.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,62 @@ describe('Graph', () => {
res.cost.must.equal(0);
});

it('returns the same path if a node which is not part of the shortest path is avoided',
() => {
const route = new Graph({
a: { b: 1 },
b: { a: 1, c: 1 },
c: { b: 1, d: 1 },
d: { c: 1 },
});

const path = route.path('a', 'c', { cost: true });
const path2 = route.path('a', 'c', { avoid: ['d'], cost: true });

path.must.eql(path2);
});

it('returns a different path if a node which is part of the shortest path is avoided',
() => {
const route = new Graph({
a: { b: 1, c: 50 },
b: { a: 1, c: 1 },
c: { a: 50, b: 1, d: 1 },
d: { c: 1 },
});

const res = route.path('a', 'd', { cost: true });
const res2 = route.path('a', 'd', { avoid: ['b'], cost: true });

res.path.must.not.eql(res.path2);
res.cost.must.be.at.most(res2.cost);
});

it('throws an error if the start node is avoided',
() => {
const route = new Graph(vertices);

demand(() => route.path('a', 'c', { avoid: ['a'] })).throw(Error);
});

it('throws an error if the end node is avoided',
() => {
const route = new Graph(vertices);

demand(() => route.path('a', 'c', { avoid: ['c'] })).throw(Error);
});

it('returns the same path and cost if a node which is not part of the graph is avoided',
() => {
const route = new Graph(vertices);

const res = route.path('a', 'c', { cost: true });
const res2 = route.path('a', 'c', { avoid: ['z'], cost: true });

res.path.must.eql(res2.path);
res.cost.must.equal(res2.cost);
});

it('works with a more complicated graph', () => {
const route = new Graph({
a: { b: 7, c: 9, f: 14 },
Expand Down

0 comments on commit 29b9924

Please sign in to comment.