Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
65 changes: 65 additions & 0 deletions lib/commonGraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
var Graph = function(){
this.graph = {};
};

Graph.prototype = {
addVertex : function(vertex){
this.graph[vertex] = this.graph[vertex] || [];
},
hasEdgeBetween : function(head, tail){
return this.graph[head].indexOf(tail)>=0;
},
order : function(){
return Object.keys(this.graph).length;
},
numberOfEdges : function(){
var graph = this.graph;
return Object.keys(graph).reduce(function(size, vertex){
return size+graph[vertex].length;
},0);
},
pathBetween : function(head, tail, visitedVertices){
visitedVertices = visitedVertices || [];
var graph = this.graph;
if(head == tail)
return visitedVertices.concat(head);
for (vertex in graph[head]){
var nextHead = graph[head][vertex];
if(visitedVertices.indexOf(nextHead)==-1){
var path = this.pathBetween(nextHead, tail, visitedVertices.concat(head));
if(path.length)
return path;
}
};
return [];
},
farthestVertex : function(head){
var vertices = Object.keys(this.graph);
var distance = 0;
var self = this;
return vertices.reduce(function(farthestVertex, vertex){
var path = self.pathBetween(head, vertex);
if(path.length>=distance){
distance = path.length;
return vertex;
}
return farthestVertex;
},'');
},
allPaths : function(head, tail, visitedVertices, paths){
visitedVertices = visitedVertices || [];
paths = paths || [];
var graph = this.graph;
if(head == tail)
return paths.push(visitedVertices.concat(head));
for (vertex in graph[head]){
var nextHead = graph[head][vertex];
if(visitedVertices.indexOf(nextHead)==-1){
this.allPaths(nextHead, tail, visitedVertices.concat(head), paths);
}
};
return paths;
}
};

module.exports = Graph;
36 changes: 36 additions & 0 deletions lib/graph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
var Graph = require('./commonGraph.js')

var graphs = {};

graphs.DirectedGraph = function(){
this.graph = {};
};

graphs.DirectedGraph.prototype = new Graph();

graphs.DirectedGraph.prototype.addEdge = function(head, tail){
this.graph[head] && this.graph[head].push(tail);
};

graphs.DirectedGraph.prototype.size = function(){
return this.numberOfEdges();
};

graphs.UndirectedGraph = function(){
this.graph = {};
}

graphs.UndirectedGraph.prototype = new Graph();

graphs.UndirectedGraph.prototype.addEdge = function(head, tail){
this.graph[head].push(tail);
if(!this.graph[tail])
this.graph[tail] = [];
this.graph[tail].push(head);
};

graphs.UndirectedGraph.prototype.size = function(){
return this.numberOfEdges()/2;
};

module.exports = graphs;
76 changes: 76 additions & 0 deletions lib/weightedGraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
var graphs = {};
var Graph = require('./commonGraph')

graphs.WeightedGraph = function(){
this.graph ={};
};

graphs.WeightedGraph.prototype = new Graph();

graphs.WeightedGraph.prototype.addEdge = function(edge){
this.graph[edge.head].push(edge);
};

var getVertices = function(graph) {
return Object.keys(graph);
};

var getEdges = function(graph) {
return Object.assign({}, graph);
};

var initiateDistances = function (vertices, head) {
var distances = {};
for (vertex of vertices)
distances[vertex] = Infinity;
distances[head] = 0;
return distances;
};

var getMinimal = function (distance,vertices) {
return vertices.reduce(function (minimal,vertex) {
if(distance[minimal] > distance[vertex] && vertices.indexOf(vertex) >=0)
return vertex;
return minimal;
});
};

var removeExecuted = function(edges, vertices, vertexToRemove) {
delete edges[vertexToRemove];
vertices.splice(vertices.indexOf(vertexToRemove),1);
};

var getPath = function (parent, head, tail, path) {
path = path || [];
if(parent[tail] == tail) return path.reverse();
return getPath(parent, head, parent[tail].head, path.concat(parent[tail]));
};

graphs.WeightedGraph.prototype.shortestPath = function(head, tail) {
var vertices = getVertices(this.graph);
var edges = getEdges(this.graph);
var distance = initiateDistances(vertices,head);
var parent = {};
parent[head] = head;
while (vertices.length){
var currentVertex = getMinimal(distance,vertices);
edges[currentVertex].forEach(function (edge) {
var newDistance = distance[currentVertex] + edge.weight;
if(distance[edge.tail] > newDistance){
distance[edge.tail] = newDistance;
parent[edge.tail] = edge;
};
});
removeExecuted(edges,vertices,currentVertex);
};
return getPath(parent, head, tail);
};

graphs.Edge = function(edge, head, tail, weight){
this.edge = edge;
this.head = head;
this.tail = tail;
this.weight = weight;
};

module.exports = graphs;
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "graphs",
"version": "1.0.0",
"description": "A set of javascript tests designed to get you to implement graphs.",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha tests/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/SRJPN/graphs.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/SRJPN/graphs/issues"
},
"homepage": "https://github.com/SRJPN/graphs#readme",
"dependencies": {
"chai": "^3.4.1"
}
}
95 changes: 95 additions & 0 deletions tests/weightedGraphsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,50 @@ var graphs=require('../lib/weightedGraph');
var assert=require('chai').assert;
var ld=require('lodash');

var denseGraph=function() {
var g=new graphs.WeightedGraph();
var vertices=['A','B','C','D','E','F','G','H','I','J'];

vertices.forEach(function(vertex){
g.addVertex(vertex);
});

for (var i = 0; i < vertices.length-1; i++) {
var from=vertices[i];
for (var j = i+1; j < vertices.length; j++) {
var edge = new graphs.Edge(from+vertices[j],from,vertices[j],1);
g.addEdge(edge);
var returnEdge = new graphs.Edge(vertices[j]+from,vertices[j],from,1);
g.addEdge(returnEdge);
}
}
return g;
};

var complexGraph = function(){
var g = new graphs.WeightedGraph();
var vertices=['A','B','C','D','E','F'];
vertices.forEach(function(vertex){
g.addVertex(vertex);
});
var edges = { AB : new graphs.Edge('AB','A','B',10),
AC : new graphs.Edge('AC','A','C',9),
AE : new graphs.Edge('AE','A','E',14),
BD : new graphs.Edge('BD','B','D',10),
CD : new graphs.Edge('CD','C','D',8),
CE : new graphs.Edge('CE','C','E',4),
DF : new graphs.Edge('DF','D','F',2),
EF : new graphs.Edge('EF','E','F',7),
CF : new graphs.Edge('CF','C','F',5),
};
for (var i = 0; i < vertices.length; i++) {
for (var j = 0; j < vertices.length; j++) {
var edge = vertices[i]+vertices[j];
edges[edge] && g.addEdge(edges[edge]);
};
};
return g;
};

describe("shortest path",function(){
it("should choose the only path when only one path exists",function(){
Expand Down Expand Up @@ -68,5 +112,56 @@ describe("shortest path",function(){
assert.equal(1,path.length);
assert.deepEqual(e1,path[0]);
});
it("should give the shortest path for a dense graph", function(){
this.timeout(10000)
var g=denseGraph();
var path = g.shortestPath('A','B');
var vertices=['A','B','C','D','E','F','G','H','I','J'];
var edges = ['AB','CB'];
assert.equal(path.length,1);
for (var i = 0; i < path.length; i++) {
assert.equal(path[i].edge,edges[i]);
};
});
it("should give the shortest path for given complex graph",function(){
var g = complexGraph();
var shortAB = g.shortestPath('A','B');
assert.equal(shortAB.length,1);
assert.equal(shortAB[0].edge, 'AB');

var shortAC = g.shortestPath('A','C');
assert.equal(shortAC.length,1);
assert.equal(shortAC[0].edge, 'AC');

var shortAF = g.shortestPath('A','F');
assert.equal(shortAF.length,2);
assert.equal(shortAF[0].edge, 'AC');
assert.equal(shortAF[1].edge, 'CF')

var shortAD = g.shortestPath('A','D');
assert.equal(shortAD.length,2);
assert.equal(shortAD[0].edge, 'AC');
assert.equal(shortAD[1].edge, 'CD');

});
it("should work for multi-graph",function(){
var g = new graphs.WeightedGraph();
var vertices=['A','B','C','D'];
vertices.forEach(function(vertex){
g.addVertex(vertex);
});
var AB = new graphs.Edge('AB','A','B',10);
var AC = new graphs.Edge('AC','A','C',9);
var BD = new graphs.Edge('BD','B','D',10);
var CD = new graphs.Edge('CD','C','D',8);
var AB1 = new graphs.Edge('AB1','A','B',5);

g.addEdge(AB1);
g.addEdge(AC);
g.addEdge(BD);
g.addEdge(CD);
g.addEdge(AB);

assert.deepEqual(g.shortestPath('A','D'),[AB1,BD]);
});
});