From f45728c89d00c606fb53782e782d2c3c5e4e8b34 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 09:41:34 +0530 Subject: [PATCH 01/17] implemented hasEdgesBetween Implemented hasEdgesBetween which gives boolean value according to the presence of edge. Also implemented addVertex which add the vertex and addEdge which creates edge between two vertices as a part of implementing the basic graph. --- .gitignore | 1 + lib/graph.js | 21 +++++++++++++++++++++ package.json | 25 +++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 .gitignore create mode 100644 lib/graph.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/lib/graph.js b/lib/graph.js new file mode 100644 index 0000000..e11b6e5 --- /dev/null +++ b/lib/graph.js @@ -0,0 +1,21 @@ +var graphs = {}; + +graphs.DirectedGraph = function(){ + this.graph = new Object; +} + +graphs.DirectedGraph.prototype = { + addVertex : function(vertex){ + console.log(this.graph); + this.graph[vertex] = this.graph[vertex] || []; + }, + addEdge : function(head, tail){ + this.graph[head].push(tail); + }, + hasEdgeBetween : function(head, tail){ + return this.graph[head].indexOf(tail)>=0; + } + +} + +module.exports = graphs; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d966d99 --- /dev/null +++ b/package.json @@ -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" + } +} From 54a913d1fe597edc6b1f91d5661f657ed4c5406a Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 09:46:34 +0530 Subject: [PATCH 02/17] Implemented order method Implemented order method which gives the order of the graph. It is the number of vertices in the graph. --- lib/graph.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/graph.js b/lib/graph.js index e11b6e5..3ee7388 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -14,6 +14,9 @@ graphs.DirectedGraph.prototype = { }, hasEdgeBetween : function(head, tail){ return this.graph[head].indexOf(tail)>=0; + }, + order : function(){ + return Object.keys(this.graph).length; } } From dea1458dd804fbc1b3c3960153a096d084c3b57f Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 09:50:48 +0530 Subject: [PATCH 03/17] Implemented size method Implemented size method which gives the size of the graph. It is basically the number of edges in the graph. --- lib/graph.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/graph.js b/lib/graph.js index 3ee7388..234f381 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -6,7 +6,6 @@ graphs.DirectedGraph = function(){ graphs.DirectedGraph.prototype = { addVertex : function(vertex){ - console.log(this.graph); this.graph[vertex] = this.graph[vertex] || []; }, addEdge : function(head, tail){ @@ -17,6 +16,12 @@ graphs.DirectedGraph.prototype = { }, order : function(){ return Object.keys(this.graph).length; + }, + size : function(){ + var graph = this.graph; + return Object.keys(graph).reduce(function(size, vertex){ + return size+graph[vertex].length; + },0); } } From 49114ec436855091102f391d9983dd25a3740b9a Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 10:12:11 +0530 Subject: [PATCH 04/17] Implemented pathBetween Implemented pathBetween which returns a path between the given vertices. There is no guarantee that the shortest or farthest will be returned. --- lib/graph.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/graph.js b/lib/graph.js index 234f381..f101c28 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -22,6 +22,25 @@ graphs.DirectedGraph.prototype = { 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 this.graph[head]){ + if(this.graph[head][vertex] == tail) + return visitedVertices.concat([head,tail]); + } + 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 []; } } From 96d9ec8551e6f6d734b94d90df4a727e8fc7ae3f Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 10:43:07 +0530 Subject: [PATCH 05/17] Implemented farthestVertex Implemented farthestVertex which returns the farthest vertex from a given vertex. --- lib/graph.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/graph.js b/lib/graph.js index f101c28..0a9ed54 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -28,8 +28,8 @@ graphs.DirectedGraph.prototype = { var graph = this.graph; if(head == tail) return visitedVertices.concat(head); - for( vertex in this.graph[head]){ - if(this.graph[head][vertex] == tail) + for( vertex in graph[head]){ + if(graph[head][vertex] == tail) return visitedVertices.concat([head,tail]); } for (vertex in graph[head]){ @@ -41,6 +41,19 @@ graphs.DirectedGraph.prototype = { } }; 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; + },''); } } From f1d9a820840bc0b5d45604d5ab519a2485793f89 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 11:09:13 +0530 Subject: [PATCH 06/17] Implemented undirectedGraph Implemented undirectedGraph which has all methods as directed graph with a little changes in the methods. --- lib/graph.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lib/graph.js b/lib/graph.js index 0a9ed54..57bd4a1 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -58,4 +58,66 @@ graphs.DirectedGraph.prototype = { } +graphs.UndirectedGraph = function(){ + this.graph = new Object; +} + +graphs.UndirectedGraph.prototype = { + addVertex : function(vertex){ + this.graph[vertex] = this.graph[vertex] || []; + }, + addEdge : function(head, tail){ + this.graph[head].push(tail); + if(!this.graph[tail]) + this.graph[tail] = []; + this.graph[tail].push(head); + }, + hasEdgeBetween : function(head, tail){ + return this.graph[head].indexOf(tail)>=0; + }, + order : function(){ + return Object.keys(this.graph).length; + }, + size : function(){ + var graph = this.graph; + var size = Object.keys(graph).reduce(function(size, vertex){ + return size+graph[vertex].length; + },0); + return size/2; + }, + pathBetween : function(head, tail, visitedVertices){ + visitedVertices = visitedVertices || []; + var graph = this.graph; + if(head == tail) + return visitedVertices.concat(head); + for( vertex in graph[head]){ + if(graph[head][vertex] == tail) + return visitedVertices.concat([head,tail]); + } + 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; + },''); + } + +} + module.exports = graphs; \ No newline at end of file From a1243a90d36e5b66be5d66d47cfef3b3b10c22aa Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 13:00:30 +0530 Subject: [PATCH 07/17] Implemented allPaths Implemented allPaths which gives all the paths between two given vertices. It is working with dense graph also. But may need further optimisation. --- lib/graph.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/graph.js b/lib/graph.js index 57bd4a1..a0fad52 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -9,7 +9,7 @@ graphs.DirectedGraph.prototype = { this.graph[vertex] = this.graph[vertex] || []; }, addEdge : function(head, tail){ - this.graph[head].push(tail); + this.graph[head] && this.graph[head].push(tail); }, hasEdgeBetween : function(head, tail){ return this.graph[head].indexOf(tail)>=0; @@ -28,10 +28,6 @@ graphs.DirectedGraph.prototype = { var graph = this.graph; if(head == tail) return visitedVertices.concat(head); - for( vertex in graph[head]){ - if(graph[head][vertex] == tail) - return visitedVertices.concat([head,tail]); - } for (vertex in graph[head]){ var nextHead = graph[head][vertex]; if(visitedVertices.indexOf(nextHead)==-1){ @@ -54,10 +50,25 @@ graphs.DirectedGraph.prototype = { } 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; } } + graphs.UndirectedGraph = function(){ this.graph = new Object; } From 0533fcfea1a67bb2f5f94044378cbf43a0e84066 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 13:03:53 +0530 Subject: [PATCH 08/17] Implemented methods for undirected graph Implemented all the methods that directed graph has to undirected graph. All tests are passing. --- lib/graph.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/graph.js b/lib/graph.js index a0fad52..070788b 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -65,7 +65,6 @@ graphs.DirectedGraph.prototype = { }; return paths; } - } @@ -127,8 +126,34 @@ graphs.UndirectedGraph.prototype = { } return farthestVertex; },''); + }, + 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 = graphs; \ No newline at end of file From 3b3a24aba7a2ab24f558961edfcb54bbdc1a09d5 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 14:17:35 +0530 Subject: [PATCH 09/17] Refactored Graphs Instead of repeating the prototype twice for each undirected and directed graph, now i have created a graph object which has the methods that are common for directed and undirected graphs. The additional methods are added to the constructors seperately. --- lib/graph.js | 132 ++++++++++++++------------------------------------- 1 file changed, 36 insertions(+), 96 deletions(-) diff --git a/lib/graph.js b/lib/graph.js index 070788b..93d2181 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -1,28 +1,19 @@ var graphs = {}; -graphs.DirectedGraph = function(){ - this.graph = new Object; +var Graph = function(){ + this.graph = {}; } -graphs.DirectedGraph.prototype = { +Graph.prototype = { addVertex : function(vertex){ this.graph[vertex] = this.graph[vertex] || []; }, - addEdge : function(head, tail){ - this.graph[head] && this.graph[head].push(tail); - }, hasEdgeBetween : function(head, tail){ return this.graph[head].indexOf(tail)>=0; }, order : function(){ return Object.keys(this.graph).length; }, - size : 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; @@ -67,93 +58,42 @@ graphs.DirectedGraph.prototype = { } } +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(){ + var graph = this.graph; + return Object.keys(graph).reduce(function(size, vertex){ + return size+graph[vertex].length; + },0); +}; graphs.UndirectedGraph = function(){ - this.graph = new Object; + this.graph = {}; } -graphs.UndirectedGraph.prototype = { - addVertex : function(vertex){ - this.graph[vertex] = this.graph[vertex] || []; - }, - addEdge : function(head, tail){ - this.graph[head].push(tail); - if(!this.graph[tail]) - this.graph[tail] = []; - this.graph[tail].push(head); - }, - hasEdgeBetween : function(head, tail){ - return this.graph[head].indexOf(tail)>=0; - }, - order : function(){ - return Object.keys(this.graph).length; - }, - size : function(){ - var graph = this.graph; - var size = Object.keys(graph).reduce(function(size, vertex){ - return size+graph[vertex].length; - },0); - return size/2; - }, - pathBetween : function(head, tail, visitedVertices){ - visitedVertices = visitedVertices || []; - var graph = this.graph; - if(head == tail) - return visitedVertices.concat(head); - for( vertex in graph[head]){ - if(graph[head][vertex] == tail) - return visitedVertices.concat([head,tail]); - } - 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; - },''); - }, - 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; - } -} +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(){ + var graph = this.graph; + var size = Object.keys(graph).reduce(function(size, vertex){ + return size+graph[vertex].length; + },0); + return size/2; +}; module.exports = graphs; \ No newline at end of file From 7fefdf2ab7306b3f87a97d26ec00179be90a8faf Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Sat, 23 Jan 2016 14:25:47 +0530 Subject: [PATCH 10/17] Refactored : size; numberOfEdges The size method in both directed and undirected graphs were similar except one of them was returning the half of the caluculated value. To avoid the repeatation of code twice, created numberOfEdges method in graph and edited size method in directed and undirected graphs. --- lib/graph.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/graph.js b/lib/graph.js index 93d2181..004abe2 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -14,6 +14,12 @@ Graph.prototype = { 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; @@ -69,10 +75,7 @@ graphs.DirectedGraph.prototype.addEdge = function(head, tail){ }; graphs.DirectedGraph.prototype.size = function(){ - var graph = this.graph; - return Object.keys(graph).reduce(function(size, vertex){ - return size+graph[vertex].length; - },0); + return this.numberOfEdges(); }; graphs.UndirectedGraph = function(){ @@ -89,11 +92,7 @@ graphs.UndirectedGraph.prototype.addEdge = function(head, tail){ }; graphs.UndirectedGraph.prototype.size = function(){ - var graph = this.graph; - var size = Object.keys(graph).reduce(function(size, vertex){ - return size+graph[vertex].length; - },0); - return size/2; + return this.numberOfEdges()/2; }; module.exports = graphs; \ No newline at end of file From 0078896962c557a46f95567658788886ec35f95f Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Mon, 25 Jan 2016 14:08:16 +0530 Subject: [PATCH 11/17] Implemented simple shortestPath in weightedGraph Implemented a simple shortestPath method in weightedGraph which gives the shortest path between 2 given vertices in weightedGraph. Also Implemented some necessary objects such as edge for the weightedGraph. --- lib/graph.js | 8 ++++---- lib/weightedGraph.js | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 lib/weightedGraph.js diff --git a/lib/graph.js b/lib/graph.js index 004abe2..6477470 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -1,10 +1,10 @@ var graphs = {}; -var Graph = function(){ +graphs.Graph = function(){ this.graph = {}; } -Graph.prototype = { +graphs.Graph.prototype = { addVertex : function(vertex){ this.graph[vertex] = this.graph[vertex] || []; }, @@ -68,7 +68,7 @@ graphs.DirectedGraph = function(){ this.graph = {}; }; -graphs.DirectedGraph.prototype = new Graph(); +graphs.DirectedGraph.prototype = new graphs.Graph(); graphs.DirectedGraph.prototype.addEdge = function(head, tail){ this.graph[head] && this.graph[head].push(tail); @@ -82,7 +82,7 @@ graphs.UndirectedGraph = function(){ this.graph = {}; } -graphs.UndirectedGraph.prototype = new Graph(); +graphs.UndirectedGraph.prototype = new graphs.Graph(); graphs.UndirectedGraph.prototype.addEdge = function(head, tail){ this.graph[head].push(tail); diff --git a/lib/weightedGraph.js b/lib/weightedGraph.js new file mode 100644 index 0000000..2dd34fd --- /dev/null +++ b/lib/weightedGraph.js @@ -0,0 +1,48 @@ +var graphs = {}; +var Graph = require('./graph').Graph; + +graphs.WeightedGraph = function(){ + this.graph ={}; +}; + +graphs.WeightedGraph.prototype = new Graph(); + +graphs.WeightedGraph.prototype.addEdge = function(edge){ + this.graph[edge.head].push(edge); +}; + +var allPaths = function(head, tail, paths, path){ + path = path || []; + var vertex = this.graph[head]; + for (var i = 0; i < vertex.length; i++) { + if(vertex[i].tail == tail){ + var newPath = path.concat(vertex[i]); + newPath.pathLength = (path.pathLength || 0) + vertex[i].length; + paths.push(newPath); + } + else{ + var newPath = path.concat(vertex[i]); + newPath.pathLength = (path.pathLength || 0) + vertex[i].length; + allPaths.apply(this,[vertex[i].tail, tail, paths, newPath]); + } + }; + return paths; +} + +graphs.WeightedGraph.prototype.shortestPath = function(head, tail){ + var paths = []; + allPaths.apply(this,[head, tail, paths]) + var shortestPath = paths.reduce(function(shortestPath, path){ + return shortestPath.pathLength>path.pathLength ? path : shortestPath; + }); + return shortestPath; +} + +graphs.Edge = function(edge, head, tail, length){ + this.edge = edge; + this.head = head; + this.tail = tail; + this.length = length; +} + +module.exports = graphs; From 251b1a8dd42f193e14040ac2a27ffefe089abfbf Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Mon, 25 Jan 2016 15:27:11 +0530 Subject: [PATCH 12/17] Modified shortestPath Modified and refactored shortestPath of weightedGraph to work with the graphs having loops. A new test of dense graph is added to prove the working of shortestPath on very complicated dense graph. --- lib/weightedGraph.js | 34 ++++++++++++++++------------------ tests/weightedGraphsTest.js | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/lib/weightedGraph.js b/lib/weightedGraph.js index 2dd34fd..084ba6b 100644 --- a/lib/weightedGraph.js +++ b/lib/weightedGraph.js @@ -11,38 +11,36 @@ graphs.WeightedGraph.prototype.addEdge = function(edge){ this.graph[edge.head].push(edge); }; -var allPaths = function(head, tail, paths, path){ +var allPaths = function(head, tail, paths, path, visitedVertices){ + paths = paths || []; path = path || []; + visitedVertices = visitedVertices || []; var vertex = this.graph[head]; for (var i = 0; i < vertex.length; i++) { - if(vertex[i].tail == tail){ - var newPath = path.concat(vertex[i]); - newPath.pathLength = (path.pathLength || 0) + vertex[i].length; - paths.push(newPath); - } - else{ - var newPath = path.concat(vertex[i]); - newPath.pathLength = (path.pathLength || 0) + vertex[i].length; - allPaths.apply(this,[vertex[i].tail, tail, paths, newPath]); - } + var newPath = path.concat(vertex[i]); + newPath.pathLength = (path.pathLength || 0) + vertex[i].length; + if(vertex[i].tail == tail) + paths.push(newPath) + else if(visitedVertices.indexOf(vertex[i].tail)==-1){ + visitedVertices.push(vertex[i].tail); + allPaths.apply(this,[vertex[i].tail,tail, paths, newPath, visitedVertices]); + }; }; return paths; -} +}; graphs.WeightedGraph.prototype.shortestPath = function(head, tail){ - var paths = []; - allPaths.apply(this,[head, tail, paths]) - var shortestPath = paths.reduce(function(shortestPath, path){ + var paths = allPaths.apply(this,[head, tail]); + return paths.reduce(function(shortestPath, path){ return shortestPath.pathLength>path.pathLength ? path : shortestPath; }); - return shortestPath; -} +}; graphs.Edge = function(edge, head, tail, length){ this.edge = edge; this.head = head; this.tail = tail; this.length = length; -} +}; module.exports = graphs; diff --git a/tests/weightedGraphsTest.js b/tests/weightedGraphsTest.js index 793c9ab..98611db 100644 --- a/tests/weightedGraphsTest.js +++ b/tests/weightedGraphsTest.js @@ -2,6 +2,29 @@ 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); + }); + var edge = new graphs.Edge('AB','A','B',100); + g.addEdge(edge); + var edge1 = new graphs.Edge('AC','A','C',1); + g.addEdge(edge1); + var edge2 = new graphs.Edge('JB','J','B',1); + g.addEdge(edge2); + + for (var i = 1; 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); + } + } + return g; +}; describe("shortest path",function(){ it("should choose the only path when only one path exists",function(){ @@ -68,5 +91,14 @@ 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(){ + var g=denseGraph(); + var path = g.shortestPath('A','B'); + var vertices=['A','B','C','D','E','F','G','H','I','J']; + var edges = ['AC','CD','DE','EF','FG','GH','HI','IJ','JB']; + assert.equal(path.length,9); + for (var i = 0; i < edges.length; i++) { + assert.equal(path[i].edge,edges[i]); + }; + }); }); From f0f9cb4e48d0dd091a27353e92065110fd3f7730 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Wed, 27 Jan 2016 11:41:49 +0530 Subject: [PATCH 13/17] Modified dense graph test Modified dense graph test a little. All tests are passing. --- tests/weightedGraphsTest.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/weightedGraphsTest.js b/tests/weightedGraphsTest.js index 98611db..61c1dc0 100644 --- a/tests/weightedGraphsTest.js +++ b/tests/weightedGraphsTest.js @@ -9,18 +9,14 @@ var denseGraph=function() { vertices.forEach(function(vertex){ g.addVertex(vertex); }); - var edge = new graphs.Edge('AB','A','B',100); - g.addEdge(edge); - var edge1 = new graphs.Edge('AC','A','C',1); - g.addEdge(edge1); - var edge2 = new graphs.Edge('JB','J','B',1); - g.addEdge(edge2); - for (var i = 1; i < vertices.length-1; i++) { + 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; @@ -95,9 +91,9 @@ describe("shortest path",function(){ var g=denseGraph(); var path = g.shortestPath('A','B'); var vertices=['A','B','C','D','E','F','G','H','I','J']; - var edges = ['AC','CD','DE','EF','FG','GH','HI','IJ','JB']; - assert.equal(path.length,9); - for (var i = 0; i < edges.length; i++) { + var edges = ['AB','CB']; + assert.equal(path.length,1); + for (var i = 0; i < path.length; i++) { assert.equal(path[i].edge,edges[i]); }; }); From e25d7bb3a570f192cb71ad6e2dba23fd08f7aa47 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Wed, 27 Jan 2016 11:58:22 +0530 Subject: [PATCH 14/17] Implemented new object Path Instead of altering the array by adding a key called pathLength inside the array, now it is using a object called Path. Path is a simple object which has a path and length of tha path. It can be further developed as per requirement. --- lib/weightedGraph.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/weightedGraph.js b/lib/weightedGraph.js index 084ba6b..a945268 100644 --- a/lib/weightedGraph.js +++ b/lib/weightedGraph.js @@ -11,14 +11,26 @@ graphs.WeightedGraph.prototype.addEdge = function(edge){ this.graph[edge.head].push(edge); }; +var Path = function(path, length){ + this.path = path || []; + this.length = length || 0; +}; + +Path.prototype ={ + concatPath : function(point){ + newPath = this.path.concat(point); + newLength = this.length + point.length; + return new Path(newPath, newLength); + } +}; + var allPaths = function(head, tail, paths, path, visitedVertices){ paths = paths || []; - path = path || []; + path = path || new Path(); visitedVertices = visitedVertices || []; var vertex = this.graph[head]; for (var i = 0; i < vertex.length; i++) { - var newPath = path.concat(vertex[i]); - newPath.pathLength = (path.pathLength || 0) + vertex[i].length; + var newPath = path.concatPath(vertex[i]); if(vertex[i].tail == tail) paths.push(newPath) else if(visitedVertices.indexOf(vertex[i].tail)==-1){ @@ -31,9 +43,10 @@ var allPaths = function(head, tail, paths, path, visitedVertices){ graphs.WeightedGraph.prototype.shortestPath = function(head, tail){ var paths = allPaths.apply(this,[head, tail]); - return paths.reduce(function(shortestPath, path){ - return shortestPath.pathLength>path.pathLength ? path : shortestPath; + var shortestPath = paths.reduce(function(shortestPath, path){ + return shortestPath.length>path.length ? path : shortestPath; }); + return shortestPath.path; }; graphs.Edge = function(edge, head, tail, length){ From d917ee3a3817b587e84b653b78ce824d77827cec Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Wed, 27 Jan 2016 15:53:54 +0530 Subject: [PATCH 15/17] Implemented shortestPath : Digestra's algorithm Implemented and tested the shortestPath method of weightedGraph using Digestra's algorithm. Added more tests to check the working of algorithm with dense, complex and multi graphs. --- lib/weightedGraph.js | 83 ++++++++++++++++++++++--------------- tests/weightedGraphsTest.js | 67 ++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 33 deletions(-) diff --git a/lib/weightedGraph.js b/lib/weightedGraph.js index a945268..e5724a9 100644 --- a/lib/weightedGraph.js +++ b/lib/weightedGraph.js @@ -11,42 +11,59 @@ graphs.WeightedGraph.prototype.addEdge = function(edge){ this.graph[edge.head].push(edge); }; -var Path = function(path, length){ - this.path = path || []; - this.length = length || 0; -}; - -Path.prototype ={ - concatPath : function(point){ - newPath = this.path.concat(point); - newLength = this.length + point.length; - return new Path(newPath, newLength); - } -}; - -var allPaths = function(head, tail, paths, path, visitedVertices){ - paths = paths || []; - path = path || new Path(); - visitedVertices = visitedVertices || []; - var vertex = this.graph[head]; - for (var i = 0; i < vertex.length; i++) { - var newPath = path.concatPath(vertex[i]); - if(vertex[i].tail == tail) - paths.push(newPath) - else if(visitedVertices.indexOf(vertex[i].tail)==-1){ - visitedVertices.push(vertex[i].tail); - allPaths.apply(this,[vertex[i].tail,tail, paths, newPath, visitedVertices]); - }; - }; - return paths; +var getVertices = function(graph) { + return Object.keys(graph); +}; + +var getEdges = function(graph) { + return Object.assign({}, graph); }; -graphs.WeightedGraph.prototype.shortestPath = function(head, tail){ - var paths = allPaths.apply(this,[head, tail]); - var shortestPath = paths.reduce(function(shortestPath, path){ - return shortestPath.length>path.length ? path : shortestPath; +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; }); - return shortestPath.path; +}; + +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.length; + 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, length){ diff --git a/tests/weightedGraphsTest.js b/tests/weightedGraphsTest.js index 61c1dc0..729b184 100644 --- a/tests/weightedGraphsTest.js +++ b/tests/weightedGraphsTest.js @@ -22,6 +22,31 @@ var denseGraph=function() { 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(){ var g=new graphs.WeightedGraph(); @@ -88,6 +113,7 @@ describe("shortest path",function(){ 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']; @@ -97,4 +123,45 @@ describe("shortest path",function(){ 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]); + }); }); From 26979100cdce5262a9234117dbd5eab9c9557f24 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Wed, 27 Jan 2016 15:59:51 +0530 Subject: [PATCH 16/17] Refactored for common names Refactored the weightedGraph object for common names such as edge, weigth etc. --- lib/weightedGraph.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/weightedGraph.js b/lib/weightedGraph.js index e5724a9..81ab88c 100644 --- a/lib/weightedGraph.js +++ b/lib/weightedGraph.js @@ -55,7 +55,7 @@ graphs.WeightedGraph.prototype.shortestPath = function(head, tail) { while (vertices.length){ var currentVertex = getMinimal(distance,vertices); edges[currentVertex].forEach(function (edge) { - var newDistance = distance[currentVertex] + edge.length; + var newDistance = distance[currentVertex] + edge.weight; if(distance[edge.tail] > newDistance){ distance[edge.tail] = newDistance; parent[edge.tail] = edge; @@ -66,11 +66,11 @@ graphs.WeightedGraph.prototype.shortestPath = function(head, tail) { return getPath(parent, head, tail); }; -graphs.Edge = function(edge, head, tail, length){ +graphs.Edge = function(edge, head, tail, weight){ this.edge = edge; this.head = head; this.tail = tail; - this.length = length; + this.weight = weight; }; module.exports = graphs; From 2c45040113505009558364dd3f28ce90b3aeb832 Mon Sep 17 00:00:00 2001 From: Sooraj Parameswaran Date: Wed, 27 Jan 2016 16:07:40 +0530 Subject: [PATCH 17/17] Refactored graphs Instead of having basic graph in graphs module, now it is in differnt module which can be required by other modules to use basic graph. --- lib/commonGraph.js | 65 ++++++++++++++++++++++++++++++++++++++++ lib/graph.js | 70 +++----------------------------------------- lib/weightedGraph.js | 2 +- 3 files changed, 70 insertions(+), 67 deletions(-) create mode 100644 lib/commonGraph.js diff --git a/lib/commonGraph.js b/lib/commonGraph.js new file mode 100644 index 0000000..e521e41 --- /dev/null +++ b/lib/commonGraph.js @@ -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; diff --git a/lib/graph.js b/lib/graph.js index 6477470..6c951e6 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -1,74 +1,12 @@ -var graphs = {}; - -graphs.Graph = function(){ - this.graph = {}; -} +var Graph = require('./commonGraph.js') -graphs.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; - } -} +var graphs = {}; graphs.DirectedGraph = function(){ this.graph = {}; }; -graphs.DirectedGraph.prototype = new graphs.Graph(); +graphs.DirectedGraph.prototype = new Graph(); graphs.DirectedGraph.prototype.addEdge = function(head, tail){ this.graph[head] && this.graph[head].push(tail); @@ -82,7 +20,7 @@ graphs.UndirectedGraph = function(){ this.graph = {}; } -graphs.UndirectedGraph.prototype = new graphs.Graph(); +graphs.UndirectedGraph.prototype = new Graph(); graphs.UndirectedGraph.prototype.addEdge = function(head, tail){ this.graph[head].push(tail); diff --git a/lib/weightedGraph.js b/lib/weightedGraph.js index 81ab88c..eee331f 100644 --- a/lib/weightedGraph.js +++ b/lib/weightedGraph.js @@ -1,5 +1,5 @@ var graphs = {}; -var Graph = require('./graph').Graph; +var Graph = require('./commonGraph') graphs.WeightedGraph = function(){ this.graph ={};