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/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 new file mode 100644 index 0000000..6c951e6 --- /dev/null +++ b/lib/graph.js @@ -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; \ No newline at end of file diff --git a/lib/weightedGraph.js b/lib/weightedGraph.js new file mode 100644 index 0000000..eee331f --- /dev/null +++ b/lib/weightedGraph.js @@ -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; 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" + } +} diff --git a/tests/weightedGraphsTest.js b/tests/weightedGraphsTest.js index 793c9ab..729b184 100644 --- a/tests/weightedGraphsTest.js +++ b/tests/weightedGraphsTest.js @@ -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(){ @@ -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]); + }); });