├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── lib ├── extras.js └── graph.js ├── package.json ├── test.html └── test ├── core.js └── extra.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/qunit"] 2 | path = vendor/qunit 3 | url = git://github.com/jquery/qunit.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - 0.4 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright © 2010 John Tantalo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the ‘Software’), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphJS 2 | 3 | [![Build Status](https://secure.travis-ci.org/tantalor/graphjs.png)](http://travis-ci.org/tantalor/graphjs) 4 | 5 | **GraphJS** is a simple Javascript library for manipulating directed and undirected [graphs](http://en.wikipedia.org/wiki/Graph_\(mathematics\)). 6 | 7 | Your graphs may have self edges, weighted edges, and directed edges, but not multiedges. 8 | 9 | ## Usage 10 | 11 | ### Creating, reading, updating, and deleting edges (CRUD) 12 | 13 | var g = new Graph(); 14 | 15 | g.set('a', 'b', 3); # creates edge (a, b) with weight 3 16 | g.get('a', 'b'); # returns 3 17 | 18 | g.set('a', 'b', 4); # changes (a, b) weight to 4 19 | g.get('a', 'b'); # returns 4 20 | 21 | g.del('a', 'b'); # removes edge (a, b) 22 | g.get('a', 'b'); # returns undefined 23 | 24 | ### Constructing graphs from data 25 | 26 | new Graph({ 27 | a: ['b', 'c'], 28 | c: ['b'], 29 | }); # triangle with vertices a, b, and c 30 | 31 | new Graph({ 32 | a: {b: 2}, 33 | b: {c: 3}, 34 | }); # path with vertices a, b, c and weights (a, b) = 2, (b, c) = 3 35 | 36 | #### With directed edges 37 | 38 | new Graph({a: ['-b', '-c']}); # Directed edges to b and c. 39 | 40 | ### Degree, size, order, and adjacency 41 | 42 | var g = new Graph({ 43 | a: ['b'], 44 | b: ['c'], 45 | }); # path with vertices a, b, c 46 | 47 | g.degree('a'); # returns 1 48 | g.degree('b'); # returns 2 49 | g.degree('c'); # returns 1 50 | 51 | g.size(); # returns 2, the number of edges 52 | 53 | g.order(); # returns 3, the number of vertices 54 | 55 | for (v in g.adj('b')) { 56 | # v = a, c (in no particular order) 57 | } 58 | 59 | ### Creating directed edges 60 | 61 | var g = new Graph(); 62 | g.dir('a', 'b'); # a ~ b, but b !~ a 63 | g.has('a', 'b'); # true 64 | g.has('b', 'a'); # false 65 | 66 | #### Alternative syntax 67 | 68 | g.set('a', '-b'); # Same as g.dir('a', 'b'); 69 | 70 | ### Deleting directed edges 71 | 72 | var g = new Graph(); 73 | g.set('a', 'b'); # a ~ b, and b ~ a 74 | g.deldir('b', 'a'); # remove b ~ a 75 | g.has('a', 'b'); # true 76 | g.has('b', 'a'); # false 77 | 78 | ### Copying 79 | 80 | var g = new Graph({ 81 | a: ['b', 'c'], 82 | c: ['d'], 83 | }); 84 | 85 | var h = g.copy(); # an independent copy of g 86 | 87 | ## Directed edges and graph size 88 | 89 | You may mix directed and undirected edges in the same graph. 90 | 91 | A pair of directed edges (a, b) and (b, a) is always collapsed into an undirected edge. An undirected edge (a, b) may be expanded into a directed edge (a, b) by deleting the directed edge (b, a) with `deldir(b, a)`. 92 | 93 | For consistency, the size of a graph is defined to be the number of undirected edges plus the number of directed edges. In other words, two distinct directed edges between two distinct vertices do not count twice for the size. 94 | 95 | A directed self edge is indistinguishable from an undirected self edge. 96 | 97 | ## Tests 98 | 99 | GraphJS is packaged with `nodeunit` tests. 100 | 101 | The easiest way to run the tests is with `npm test`. 102 | 103 | $ npm test 104 | ... 105 | OK: 173 assertions (23ms) 106 | 107 | You can also test your browser by loading the `test.html` page. 108 | -------------------------------------------------------------------------------- /lib/extras.js: -------------------------------------------------------------------------------- 1 | (function () 2 | { 3 | if (typeof(window) !== 'undefined') { 4 | // browser 5 | var Graph = window.Graph; 6 | } else if (typeof(require) !== 'undefined') { 7 | // commonjs 8 | var Graph = exports.Graph = require("./graph").Graph; 9 | } else if (typeof(load) !== 'undefined') { 10 | // jsc 11 | var Graph = load('lib/graph.js').Graph; 12 | } 13 | 14 | Graph.prototype.subgraph = function (vertices) 15 | { 16 | var g = new Graph(); 17 | 18 | vertices = dict(vertices); 19 | for (var v in vertices) 20 | for (var u in this.adj(v)) 21 | if (u in vertices) 22 | g.set(this.vertex(v), this.vertex(u), this.get(v, u)); 23 | 24 | return g; 25 | } 26 | 27 | Graph.prototype.cross = function (g) 28 | { 29 | var vertices = []; 30 | 31 | for (var i = 0; i < this._vertices.length; i++) 32 | { 33 | for (var j = 0; j < g._vertices.length; j++) 34 | { 35 | vertices.push([this._vertices[i], g._vertices[j]]); 36 | } 37 | } 38 | 39 | return vertices; 40 | } 41 | 42 | Graph.prototype.cartesian = function (g) 43 | { 44 | var h = new Graph(); 45 | var vertices = this.cross(g); 46 | 47 | for (var i = 0; i < vertices.length; i++) 48 | { 49 | for (var j = 0; j < vertices.length; j++) 50 | { 51 | var u = vertices[i], v = vertices[j]; 52 | if (u[0] === v[0] && g.get(u[1], v[1]) || this.get(u[0], v[0]) && u[1] === v[1]) 53 | { 54 | h.set(u, v); 55 | } 56 | } 57 | } 58 | 59 | return h; 60 | } 61 | 62 | Graph.prototype.tensor = function (g) 63 | { 64 | var h = new Graph(); 65 | var vertices = this.cross(g); 66 | 67 | for (var i = 0; i < vertices.length; i++) 68 | { 69 | for (var j = 0; j < vertices.length; j++) 70 | { 71 | var u = vertices[i], v = vertices[j]; 72 | if (this.get(u[0], v[0]) && g.get(u[1], v[1])) 73 | h.set(u, v); 74 | } 75 | } 76 | 77 | return h; 78 | } 79 | 80 | Graph.prototype.union = function (g) 81 | { 82 | var h = this.copy(); 83 | 84 | g.each(function (v) 85 | { 86 | for (var u in g.adj(v)) 87 | { 88 | h.set(v, u, g.get(v, u)); 89 | } 90 | }); 91 | 92 | return h; 93 | } 94 | 95 | Graph.prototype.is_bipartite = function () 96 | { 97 | var seen = 0; 98 | var fringe = []; 99 | var color = {}; 100 | var vertices = dict(this._vertices); 101 | 102 | while (seen < this._vertices.length) 103 | { 104 | // populate fringe with an uncolored vertex 105 | if (fringe.length === 0) 106 | { 107 | for (var v in vertices) 108 | { 109 | // move from vertices to fringe 110 | color[v] = true; 111 | fringe.push(v); 112 | break; 113 | } 114 | } 115 | 116 | // consume v from fringe 117 | var v = fringe.shift(); 118 | delete vertices[v]; 119 | seen++; 120 | 121 | // color neighbors u of v 122 | for (var u in this.adj(v)) 123 | { 124 | // did we already color neighbor u? 125 | if (u in color) 126 | { 127 | // fails when adjacent vertices have the same color 128 | if (color[u] === color[v]) 129 | return false; 130 | } else { 131 | // give neighbor u the opposite color of v, add to fringe 132 | color[u] = !color[v]; 133 | fringe.push(u); 134 | } 135 | } 136 | } 137 | 138 | return true; 139 | } 140 | 141 | Graph.prototype.is_complete = function () 142 | { 143 | var order = this.order() - this.grep( 144 | function (v) { 145 | // remove self edges 146 | return v in this.adj(v); 147 | }).length; 148 | 149 | return this.size() === order * (order - 1) / 2; 150 | } 151 | 152 | Graph.prototype.is_cycle = function () 153 | { 154 | if (this.order() !== this.size()) 155 | return false; 156 | 157 | var visited = {}; 158 | var seen = 0; 159 | var v = this._vertices[0]; 160 | 161 | while (!(v === undefined || v in visited)) 162 | { 163 | visited[v] = 1; 164 | seen++; 165 | for (var u in this.adj(v)) 166 | if (!(u in visited)) 167 | v = u; 168 | } 169 | 170 | return seen === this.order(); 171 | } 172 | 173 | Graph.prototype.is_subgraph = function (g) 174 | { 175 | return this.grep(function (v) 176 | { 177 | for (var u in this.adj(v)) 178 | if (!g.has(v, u)) 179 | return false; 180 | 181 | return true; 182 | }).length === this.order(); 183 | } 184 | 185 | Graph.prototype.bipartite_double_cover = function () 186 | { 187 | return this.tensor(Graph.k(2)); 188 | } 189 | 190 | Graph.peterson = function (vertices) 191 | { 192 | if (vertices === undefined) 193 | vertices = [1,2,3,4,5,6,7,8,9,10]; 194 | 195 | var g = new Graph(); 196 | 197 | g.set(vertices[0], vertices[1]); 198 | g.set(vertices[1], vertices[2]); 199 | g.set(vertices[2], vertices[3]); 200 | g.set(vertices[3], vertices[4]); 201 | g.set(vertices[4], vertices[0]); 202 | 203 | g.set(vertices[5], vertices[7]); 204 | g.set(vertices[6], vertices[8]); 205 | g.set(vertices[7], vertices[9]); 206 | g.set(vertices[8], vertices[5]); 207 | g.set(vertices[9], vertices[6]); 208 | 209 | g.set(vertices[0], vertices[5]); 210 | g.set(vertices[1], vertices[6]); 211 | g.set(vertices[2], vertices[7]); 212 | g.set(vertices[3], vertices[8]); 213 | g.set(vertices[4], vertices[9]); 214 | 215 | return g; 216 | } 217 | 218 | Graph.k = function (n, vertices) 219 | { 220 | var g = new Graph(); 221 | 222 | for (var i = 0; i < n; i++) 223 | for (var j = 0; j < n; j++) 224 | if (i != j) 225 | g.set(vertices ? vertices[i] : i, vertices ? vertices[j] : j); 226 | 227 | return g; 228 | } 229 | 230 | Graph.c = function (n, vertices) 231 | { 232 | var g = new Graph(); 233 | 234 | 235 | for (var i = 0; i < n; i++) 236 | { 237 | var j = (i+1) % n; 238 | g.set(vertices ? vertices[i] : i, vertices ? vertices[j] : j); 239 | } 240 | 241 | return g; 242 | } 243 | 244 | function dict (seq, val) 245 | { 246 | if (seq.constructor === Object) 247 | return seq; 248 | 249 | var obj = {}; 250 | 251 | if (val === undefined) 252 | val = 1; 253 | 254 | for (var i = 0; i < seq.length; i++) 255 | obj[seq[i]] = val; 256 | 257 | return obj; 258 | } 259 | 260 | if (typeof(load) !== 'undefined') { 261 | // jsc 262 | return { 263 | "Graph": Graph 264 | }; 265 | } 266 | }()); 267 | -------------------------------------------------------------------------------- /lib/graph.js: -------------------------------------------------------------------------------- 1 | (function () 2 | { 3 | var ANTIEDGE = false; 4 | 5 | var Graph = function (graph) { 6 | this._graph = {}; // {u: {v: edge, ...}, ...} 7 | this._degree = {}; // {u: degree, ...} 8 | this._indegree = {}; // {u: degree, ...} 9 | this._vertices = []; // [u, v, ...] 10 | this._vertex = {}; // {u: u, ...} 11 | this._size = 0; 12 | 13 | if (graph) 14 | { 15 | // copy input graph 16 | for (var u in graph) 17 | { 18 | var adj = graph[u]; 19 | if (adj.constructor === Object) 20 | { 21 | for (var v in adj) 22 | { 23 | this.set(u, v, adj[v]); 24 | } 25 | } else if (adj.constructor === Array) 26 | { 27 | for (var i = 0; i < adj.length; i++) 28 | { 29 | this.set(u, adj[i]); 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | Graph.prototype.vertex = function (id) 37 | { 38 | if (id in this._vertex) 39 | return this._vertex[id]; 40 | } 41 | 42 | Graph.prototype.copy = function () 43 | { 44 | return new Graph(this._graph); 45 | } 46 | 47 | Graph.prototype.adj = function (u) 48 | { 49 | var adjacents = this._graph[u]; 50 | return adjacents ? adjacents : {}; 51 | } 52 | 53 | Graph.prototype.get = function (u, v) 54 | { 55 | if (this._graph[u]) 56 | return this._graph[u][v]; 57 | } 58 | 59 | Graph.prototype.has = function (u, v) 60 | { 61 | return this.get(u, v) !== undefined; 62 | } 63 | 64 | Graph.prototype.degree = function (u) 65 | { 66 | return this._degree[u]; 67 | } 68 | 69 | Graph.prototype.indegree = function (u) 70 | { 71 | return this._indegree[u]; 72 | } 73 | 74 | Graph.prototype.size = function () 75 | { 76 | return this._size; 77 | } 78 | 79 | Graph.prototype.order = function () 80 | { 81 | return this._vertices.length; 82 | } 83 | 84 | Graph.prototype.each = function (f) 85 | { 86 | for (var i = 0; i < this._vertices.length; i++) 87 | { 88 | if (f.call(this, this._vertices[i], i) === false) 89 | break; 90 | } 91 | } 92 | 93 | Graph.prototype.grep = function (f) 94 | { 95 | var vertices = []; 96 | this.each(function (v, i) 97 | { 98 | if (f.call(this, v, i)) 99 | vertices.push(v); 100 | }); 101 | return vertices; 102 | } 103 | 104 | Graph.prototype.set = function (u, v, edge) 105 | { 106 | // set('a', '-b', ...) is a synonym for dir('a', 'b', ...) 107 | if (v[0] == '-') { 108 | return this.dir(u, v.substr(1), edge); 109 | } 110 | 111 | // take an undefined edge as simply 'true' for convenience 112 | edge = (edge === undefined ? true : edge); 113 | 114 | // increment/decrement size 115 | if (edge !== ANTIEDGE && !this.has(u, v) && !this.has(v, u)) 116 | { 117 | this._size++; 118 | } else if (edge === ANTIEDGE && (this.has(u, v) || this.has(v, u))) 119 | { 120 | this._size--; 121 | } 122 | 123 | // set/unset edges and increment/decrement degrees 124 | _set(this, u, v, edge); 125 | _set(this, v, u, edge); 126 | 127 | return edge; 128 | } 129 | 130 | Graph.prototype.dir = function (u, v, edge) 131 | { 132 | // take an undefined edge as simply 'true' for convenience 133 | edge = (edge === undefined ? true : edge); 134 | 135 | // increment/decrement size 136 | if (edge !== ANTIEDGE && !(this.has(u, v) || this.has(v, u))) 137 | { 138 | this._size++; 139 | } else if (edge === ANTIEDGE && this.has(u, v) && !this.has(v, u)) 140 | { 141 | this._size--; 142 | } 143 | 144 | // set/unset edge and increment/decrement degree 145 | _set(this, u, v, edge); 146 | 147 | return edge; 148 | } 149 | 150 | Graph.prototype.drop = function (v) 151 | { 152 | if (!(v in this._degree)) 153 | return false; 154 | 155 | // remove adjacent edges 156 | for (var u in this.adj(v)) 157 | { 158 | this.del(v, u); 159 | } 160 | 161 | // remove from vertex list 162 | for (var i = 0; i < this._vertices.length; i++) 163 | { 164 | if (this._vertices[i] === v) 165 | { 166 | this._vertices.splice(i, 1); 167 | break; 168 | } 169 | } 170 | 171 | // remove from degree indexes 172 | delete this._degree[v]; 173 | delete this._indegree[v]; 174 | 175 | return true; 176 | } 177 | 178 | Graph.prototype.del = function (u, v) 179 | { 180 | // remove vertex 181 | if (v === undefined) 182 | return this.drop(u); 183 | 184 | // remove edge 185 | return this.set(u, v, ANTIEDGE); 186 | } 187 | 188 | Graph.prototype.deldir = function (u, v) 189 | { 190 | return this.dir(u, v, ANTIEDGE); 191 | } 192 | 193 | function _set (g, u, v, e) 194 | { 195 | // add to vertex list if the degree is unknown 196 | if (!(u in g._degree)) 197 | { 198 | g._vertices.push(u); 199 | g._vertex[u] = u; 200 | g._degree[u] = g._indegree[u] = 0; 201 | } 202 | 203 | if (!(v in g._degree)) 204 | { 205 | g._vertices.push(v); 206 | g._vertex[v] = v; 207 | g._degree[v] = g._indegree[v] = 0; 208 | } 209 | 210 | // we are setting an edge 211 | if (e !== ANTIEDGE) 212 | { 213 | // we have a *new* edge 214 | if (!g.has(u, v)) 215 | { 216 | g._degree[u]++; 217 | g._indegree[v]++; 218 | } 219 | 220 | // add to adjacency list 221 | g._graph[u] = g._graph[u] || {}; 222 | g._graph[u][v] = e; 223 | } 224 | // we are deleting an existing edge 225 | else if (g.has(u, v)) 226 | { 227 | // remove from adjacency list 228 | delete g._graph[u][v]; 229 | g._degree[u]--; 230 | g._indegree[v]--; 231 | } 232 | } 233 | 234 | // add to global scope 235 | if (typeof(window) !== 'undefined') { 236 | // browser 237 | window.Graph = Graph; 238 | } else if (typeof(require) !== 'undefined') { 239 | // commonjs 240 | exports.Graph = Graph; 241 | } else if (typeof(load) !== 'undefined') { 242 | // jsc 243 | return { 244 | "Graph": Graph 245 | }; 246 | } 247 | }()); 248 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graph", 3 | "description": "library for manipulating directed and undirected graphs", 4 | "version": "0.1.0", 5 | "homepage": "http://github.johntantalo.com/graphjs/", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/tantalor/graphjs.git" 9 | }, 10 | "author": "John Tantalo (http://johntantalo.com)", 11 | "main": "./lib/graph", 12 | "directories": { 13 | "lib": "./lib" 14 | }, 15 | "devDependencies": { 16 | "nodeunit": "latest" 17 | }, 18 | "scripts": { 19 | "test": "./node_modules/.bin/nodeunit test" 20 | }, 21 | "engines": { 22 | "node": "*", 23 | "jsc": "*", 24 | "narwhal": "*", 25 | "ringo": "*" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example Test Suite 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

GraphJS Test Suite

13 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/core.js: -------------------------------------------------------------------------------- 1 | if (typeof(require) !== 'undefined') { 2 | // commonjs 3 | var Graph = require("../lib/graph").Graph; 4 | } else if (typeof(load) !== 'undefined') { 5 | // jsc 6 | var Graph = load("lib/graph.js").Graph; 7 | } 8 | 9 | this.core_suite = 10 | { 11 | 'Graph exists': function (test) 12 | { 13 | test.ok(Graph, 14 | "I can find the Graph class."); 15 | test.done(); 16 | }, 17 | 18 | 'Null get': function (test) 19 | { 20 | var g = new Graph(); 21 | test.ok(g.get(1, 2) === undefined, 22 | "Get for unknown edge returns undef."); 23 | test.ok(g.has(1, 2) === false, 24 | "Has for unknown edge returns false."); 25 | test.done(); 26 | }, 27 | 28 | 'Bad delete': function (test) 29 | { 30 | var g = new Graph(); 31 | g.del(1, 2); 32 | test.ok(g.degree(1) === 0, 33 | "Degree of one vertex is 0."); 34 | test.ok(g.degree(2) === 0, 35 | "Degree of other vertex is 0."); 36 | test.ok(g.size() === 0, 37 | "Size is 0."); 38 | test.done(); 39 | }, 40 | 41 | 'Simple get': function (test) 42 | { 43 | var g = new Graph(); 44 | test.ok(g.set(1, 2, 3) === 3, 45 | "Set returns edge weight."); 46 | test.ok(g.get(1, 2) === 3, 47 | "Get returns edge weight."); 48 | test.done(); 49 | }, 50 | 51 | 'Set and get': function (test) 52 | { 53 | var g = new Graph(); 54 | g.set(1, 2, 3); 55 | test.ok(g.get(1, 2) === 3, 56 | "Get with original order returns weight."); 57 | test.ok(g.get(2, 1) === 3, 58 | "Get with reveresed order returns weight."); 59 | test.ok(g.order() === 2, 60 | "Number of vertices is 2."); 61 | test.ok(g.degree(1) === 1, 62 | "Degree of one vertex is 1."); 63 | test.ok(g.degree(2) === 1, 64 | "Degree of other vertex is 1."); 65 | test.ok(g.size() === 1, 66 | "Size is 1."); 67 | test.done(); 68 | }, 69 | 70 | 'Set and delete': function (test) 71 | { 72 | var g = new Graph(); 73 | test.ok(g.set(1, 2), 74 | "Added edge.") 75 | test.ok(g.del(1, 2) === false, 76 | "Deleted edge.") 77 | test.ok(!g.has(1, 2), 78 | "Deleted edge doesn't exist."); 79 | test.ok(!g.has(2, 1), 80 | "Reverse of edge also doesn't exist."); 81 | test.ok(g.order() === 2, 82 | "Number of vertices is 2."); 83 | test.ok(g.degree(1) === 0, 84 | "Degree of one vertex is 0."); 85 | test.ok(g.degree(2) === 0, 86 | "Degree of other vertex is 0."); 87 | test.ok(g.size() === 0, 88 | "Size is 0."); 89 | test.done(); 90 | }, 91 | 92 | 'Set and reverse set': function (test) 93 | { 94 | var g = new Graph(); 95 | g.set(1, 2, 3); 96 | g.set(2, 1, 4); 97 | test.ok(g.get(1, 2) === 4, 98 | "Get with original order returns new weight."); 99 | test.ok(g.get(2, 1) === 4, 100 | "Get with reversed order returns new weight."); 101 | test.ok(g.order() === 2, 102 | "Number of vertices is 2."); 103 | test.ok(g.degree(1) === 1, 104 | "Degree of one vertex is 1."); 105 | test.ok(g.degree(2) === 1, 106 | "Degree of other vertex is 1."); 107 | test.ok(g.size() === 1, 108 | "Size is 1."); 109 | test.done(); 110 | }, 111 | 112 | 'Set and reverse delete': function (test) 113 | { 114 | var g = new Graph(); 115 | test.ok(g.set(1, 2), 116 | "Added edge.") 117 | test.ok(g.del(2, 1) === false, 118 | "Deleted edge."); 119 | test.ok(!g.has(1, 2), 120 | "Deleted edge doesn't exist."); 121 | test.ok(!g.has(2, 1), 122 | "Reverse of edge also doesn't exist."); 123 | test.ok(g.order() === 2, 124 | "Number of vertices is 2."); 125 | test.ok(g.degree(1) === 0, 126 | "Degree of one vertex is 0."); 127 | test.ok(g.degree(2) === 0, 128 | "Degree of other vertex is 0."); 129 | test.ok(g.size() === 0, 130 | "Size is 0."); 131 | test.done(); 132 | }, 133 | 134 | 'Self edge': function (test) 135 | { 136 | var g = new Graph(); 137 | test.ok(g.set(1, 1, 2) == 2, 138 | "Set self edge returns weight."); 139 | test.ok(g.get(1, 1) === 2, 140 | "Get self edge returns weight."); 141 | test.ok(g.order() === 1, 142 | "Number of vertices is 1."); 143 | test.ok(g.degree(1) === 1, 144 | "Degree of vertex is 1."); 145 | test.ok(g.size() === 1, 146 | "Size is 1."); 147 | test.done(); 148 | }, 149 | 150 | 'Simple constructor': function (test) 151 | { 152 | var g = new Graph({pirate: ['ninja', 'robot']}); 153 | test.ok(g.get('pirate', 'ninja') && g.get('pirate', 'robot'), 154 | "All edges exist."); 155 | test.ok(g.order() === 3, 156 | "Number of vertices is 3."); 157 | test.ok(g.degree('pirate') === 2, 158 | "Degree of 'pirate' vertex is 2."); 159 | test.ok(g.degree('ninja') === 1, 160 | "Degree of 'ninja' vertex is 1."); 161 | test.ok(g.degree('robot') === 1, 162 | "Degree of 'robot' vertex is 1."); 163 | test.ok(g.size() === 2, 164 | "Size is 2."); 165 | test.done(); 166 | }, 167 | 168 | 'Constructor with weights': function (test) 169 | { 170 | var g = new Graph({pirate: {ninja: 'robot'}}); 171 | test.ok(g.get('pirate', 'ninja') === 'robot', 172 | "Get in original order has weight 'robot'."); 173 | test.ok(g.get('ninja', 'pirate') === 'robot', 174 | "Get in reversed order has weight 'robot'."); 175 | test.ok(g.order() === 2, 176 | "Number of vertices is 2."); 177 | test.ok(g.degree('pirate') === 1, 178 | "Degree of 'pirate' vertex is 1."); 179 | test.ok(g.degree('ninja') === 1, 180 | "Degree of 'ninja' vertex is 1."); 181 | test.ok(g.size() === 1, 182 | "Size is 1."); 183 | test.done(); 184 | }, 185 | 186 | 'Constructor with directed edges': function (test) 187 | { 188 | var g = new Graph({'a': ['-b', '-c']}); 189 | test.ok(g.order() === 3, 190 | "Number of vertices is 3."); 191 | test.ok(g.size() === 2, 192 | "Number of edges is 2."); 193 | test.ok(g.degree('a') === 2, 194 | "Out degree of 'a' is 1."); 195 | test.ok(g.degree('b') === 0, 196 | "Out degree of 'b' is 0."); 197 | test.ok(g.degree('c') === 0, 198 | "Out degree of 'c' is 0."); 199 | test.ok(g.indegree('a') === 0, 200 | "In degree of 'a' is 0."); 201 | test.ok(g.indegree('b') === 1, 202 | "In degree of 'b' is 1."); 203 | test.ok(g.indegree('c') === 1, 204 | "In degree of 'c' is 1."); 205 | test.ok(g.has('a', 'b')); 206 | test.ok(g.has('a', 'c')); 207 | test.ok(!g.has('b', 'a')); 208 | test.ok(!g.has('c', 'a')); 209 | test.ok(!g.has('c', 'b')); 210 | test.ok(!g.has('b', 'c')); 211 | test.done(); 212 | }, 213 | 214 | 'Multiget': function (test) 215 | { 216 | var g = new Graph(); 217 | test.ok(g.set(1, 2) && g.set(2, 3) && g.set(3, 1), 218 | "Set all edges."); 219 | test.ok(g.get(2, 1) && g.get(3, 2) && g.get(1, 3), 220 | "All edges exist."); 221 | test.ok(g.degree(1) == 2 && g.degree(2) == 2 && g.degree(3) == 2, 222 | "Degree of all vertices is 2."); 223 | test.ok(g.order() === 3, 224 | "Number of vertices is 3."); 225 | test.ok(g.size() === 3, 226 | "Size is 3."); 227 | test.done(); 228 | }, 229 | 230 | 'Adjacency': function (test) 231 | { 232 | var g = new Graph(); 233 | test.ok(typeof g.adj(3) !== 'undefined', 234 | "Should returns empty object when no adjacents."); 235 | g.set(1, 2); 236 | test.ok(1 in g.adj(2), 237 | "Vertex 1 is adjacent to vertex 2."); 238 | test.ok(2 in g.adj(1), 239 | "Vertex 2 is adjacent to vertex 1."); 240 | test.done(); 241 | }, 242 | 243 | 'Depth-first search': function (test) 244 | { 245 | var g = new Graph({ 246 | 1: [2, 3], 247 | 2: [4, 5], 248 | 3: [6, 7], 249 | }); 250 | var visited = {}; 251 | function visit (v) 252 | { 253 | if (visited[v]) return; 254 | visited[v] = 1; 255 | for (var u in g.adj(v)) { 256 | visit(u); 257 | }; 258 | } 259 | visit(1); 260 | test.ok(visited[1], "Visited vertex 1."); 261 | test.ok(visited[2], "Visited vertex 2."); 262 | test.ok(visited[3], "Visited vertex 3."); 263 | test.ok(visited[4], "Visited vertex 4."); 264 | test.ok(visited[5], "Visited vertex 5."); 265 | test.ok(visited[6], "Visited vertex 6."); 266 | test.ok(visited[7], "Visited vertex 7."); 267 | test.done(); 268 | }, 269 | 270 | 'Breadth-first search': function (test) 271 | { 272 | var g = new Graph({ 273 | 1: [2, 3], 274 | 2: [4, 5], 275 | 3: [6, 7], 276 | }); 277 | var fringe = [1]; 278 | var visited = {}; 279 | while (fringe.length > 0) 280 | { 281 | var v = fringe.shift(); 282 | if (visited[v]) continue; 283 | visited[v] = 1; 284 | for (var u in g.adj(v)) { 285 | fringe.push(u); 286 | }; 287 | } 288 | test.ok(visited[1], "Visited vertex 1."); 289 | test.ok(visited[2], "Visited vertex 2."); 290 | test.ok(visited[3], "Visited vertex 3."); 291 | test.ok(visited[4], "Visited vertex 4."); 292 | test.ok(visited[5], "Visited vertex 5."); 293 | test.ok(visited[6], "Visited vertex 6."); 294 | test.ok(visited[7], "Visited vertex 7."); 295 | test.done(); 296 | }, 297 | 298 | 'Simple copy': function (test) 299 | { 300 | var g = new Graph({ 301 | 1: [2, 3], 302 | 2: [3], 303 | }); 304 | var h = g.copy(); 305 | test.ok(g.get(2, 3), 306 | "Original graph has edge.") 307 | test.ok(g.del(2, 3) === false, 308 | "Deleted edge in original graph."); 309 | test.ok(!g.has(2, 3), 310 | "Original graph does not have deleted edge.") 311 | test.ok(h.get(2, 3), 312 | "Copied graph has deleted edge."); 313 | test.ok(g.size() == 2, 314 | "Original graph has size 2."); 315 | test.ok(h.size() == 3, 316 | "Copied graph has size 3."); 317 | test.ok(g.degree(2) == 1, 318 | "Degree of vertex 1 in original is 1.") 319 | test.ok(h.degree(2) == 2, 320 | "Degree of vertex 1 in copy is 2.") 321 | test.done(); 322 | }, 323 | 324 | 'Copy with weights': function (test) 325 | { 326 | var g = new Graph({ 327 | 1: {2: 3}, 328 | }); 329 | var h = g.copy(); 330 | test.ok(g.get(1, 2) === 3, 331 | "Original graph has edge with weight.") 332 | test.ok(g.del(1, 2) === false, 333 | "Deleted edge in original graph."); 334 | test.ok(!g.has(1, 2), 335 | "Original graph does not have deleted edge.") 336 | test.ok(h.get(1, 2) == 3, 337 | "Copied graph has deleted edge with weight."); 338 | test.done(); 339 | }, 340 | 341 | 'Repeated vertices': function (test) 342 | { 343 | var g = new Graph(); 344 | g.set('a', 'b'); 345 | g.del('a', 'b'); 346 | g.set('a', 'b'); 347 | 348 | test.ok(g.order() === 2, 349 | "Graph has 2 vertices."); 350 | test.done(); 351 | }, 352 | 353 | 'Add falsey weight': function (test) 354 | { 355 | var g = new Graph(); 356 | g.set('a', 'b', 0); 357 | 358 | test.ok(g.size() === 1, 359 | "Graph has 1 edge."); 360 | test.done(); 361 | }, 362 | 363 | 'Remove falsey weight': function (test) 364 | { 365 | var g = new Graph(); 366 | g.set('a', 'b', 0); 367 | g.del('a', 'b'); 368 | 369 | test.ok(g.size() === 0, 370 | "Graph has 0 edges."); 371 | test.done(); 372 | }, 373 | 374 | 'Add directed edges': function (test) 375 | { 376 | var g = new Graph(); 377 | g.dir(1, 2); 378 | 379 | test.ok(g.order() === 2, 380 | "Order is 2."); 381 | 382 | test.ok(g.size() === 1, 383 | "Size is 1."); 384 | 385 | test.ok(g.has(1, 2), 386 | "1 ~ 2"); 387 | 388 | test.ok(!g.has(2, 1), 389 | "2 !~ 1"); 390 | 391 | test.ok(g.degree(1) === 1, 392 | "Out degrees of 1 is 1."); 393 | 394 | test.ok(g.degree(2) === 0, 395 | "Out degrees of 2 is 0."); 396 | 397 | test.ok(g.indegree(1) === 0, 398 | "Out degrees of 1 is 0."); 399 | 400 | test.ok(g.indegree(2) === 1, 401 | "Out degrees of 2 is 1."); 402 | test.done(); 403 | }, 404 | 405 | 'Remove directed edges': function (test) 406 | { 407 | var g = new Graph(); 408 | g.set(1, 2); 409 | g.deldir(2, 1); 410 | 411 | test.ok(g.order() === 2, 412 | "Order is 2."); 413 | 414 | test.ok(g.size() === 1, 415 | "Size is 1."); 416 | 417 | test.ok(g.has(1, 2), 418 | "1 ~ 2"); 419 | 420 | test.ok(!g.has(2, 1), 421 | "2 !~ 1"); 422 | 423 | test.ok(g.degree(1) === 1, 424 | "Out degrees of 1 is 1."); 425 | 426 | test.ok(g.degree(2) === 0, 427 | "Out degrees of 2 is 0."); 428 | 429 | test.ok(g.indegree(1) === 0, 430 | "Out degrees of 1 is 0."); 431 | 432 | test.ok(g.indegree(2) === 1, 433 | "Out degrees of 2 is 1."); 434 | test.done(); 435 | }, 436 | 437 | 'Double directed edges': function (test) 438 | { 439 | var g = new Graph(); 440 | g.dir(1, 2); 441 | g.dir(2, 1); 442 | 443 | test.ok(g.order() === 2, 444 | "Order is 2."); 445 | 446 | test.ok(g.size() === 1, 447 | "Size is 1."); 448 | 449 | test.ok(g.has(1, 2) && g.has(2, 1), 450 | "1 ~ 2 and 2 ~ 1"); 451 | test.done(); 452 | }, 453 | 454 | 'Drop vertex': function (test) 455 | { 456 | // var g = Graph.k(4, ['a', 'b', 'c', 'd']); 457 | var g = new Graph({ 458 | a: ['b', 'c', 'd'], 459 | b: ['c', 'd'], 460 | c: ['d'], 461 | }); 462 | test.ok(!g.drop('z'), 463 | "Can't drop a vertex that isn't in the graph.") 464 | test.ok(g.size() === 6 && g.order() === 4, 465 | "Graph is K(4)."); 466 | test.ok(g.drop('a'), 467 | "Dropped a vertex."); 468 | test.ok(g.size() === 3 && g.order() === 3, 469 | "K(4) is now K(3).") 470 | test.ok(g.del('b'), 471 | "Dropped another vertex."); 472 | test.ok(g.size() === 1 && g.order() === 2, 473 | "K(3) is now K(2)."); 474 | test.done(); 475 | } 476 | }; 477 | -------------------------------------------------------------------------------- /test/extra.js: -------------------------------------------------------------------------------- 1 | if (typeof(require) !== 'undefined') { 2 | // commonjs 3 | var Graph = require("../lib/extras").Graph; 4 | } else if (typeof(load) !== 'undefined') { 5 | // jsc 6 | var Graph = load("lib/extras.js").Graph; 7 | } 8 | 9 | this.extra_suite = 10 | { 11 | 'Cartesian product': function (test) 12 | { 13 | var g = new Graph(); 14 | g.set(1,1); 15 | g.set(2,2); 16 | 17 | var h = new Graph(); 18 | h.set(3, 4); 19 | h.set(4, 5); 20 | h.set(5, 3); 21 | 22 | var gh = g.cartesian(h); 23 | 24 | test.ok(gh.order() === 6, 25 | "Order is 6."); 26 | test.ok(gh.size() === 12, 27 | "Size is 12."); 28 | 29 | test.ok([1, 3] in gh.adj([1, 4]), 30 | "(1,3) adjacent to (1,4)."); 31 | test.ok([1, 4] in gh.adj([1, 5]), 32 | "(1,4) adjacent to (1,5)."); 33 | test.ok([1, 5] in gh.adj([1, 3]), 34 | "(1,5) adjacent to (1,3)."); 35 | 36 | test.ok([2, 3] in gh.adj([2, 4]), 37 | "(2,3) adjacent to (2,4)."); 38 | test.ok([2, 4] in gh.adj([2, 5]), 39 | "(2,4) adjacent to (2,5)."); 40 | test.ok([2, 5] in gh.adj([2, 3]), 41 | "(2,5) adjacent to (2,3)."); 42 | 43 | test.ok(gh.grep(function (v) {return v in gh.adj(v)}).length === gh.order(), 44 | "Every vertex adjacent to self.") 45 | test.done(); 46 | }, 47 | 48 | 'Cartesian self product': function (test) 49 | { 50 | var g = new Graph({ 51 | a: ['b'], 52 | b: ['c'], 53 | c: ['a'], 54 | }); 55 | 56 | var h = g.cartesian(g); 57 | 58 | test.ok(h.size() === 18, 59 | "Size is 18."); 60 | test.ok(h.order() === 9, 61 | "Order is 9."); 62 | 63 | test.ok(['a', 'b'] in h.adj(['b', 'b']), 64 | "(a,b) is adjacent to (b,b)"); 65 | test.ok(['c', 'b'] in h.adj(['b', 'b']), 66 | "(c,b) is adjacent to (b,b)"); 67 | test.ok(['b', 'a'] in h.adj(['b', 'b']), 68 | "(b,a) is adjacent to (b,b)"); 69 | test.ok(['b', 'c'] in h.adj(['b', 'b']), 70 | "(b,c) is adjacent to (b,b)"); 71 | 72 | test.ok(h.grep(function (v) {return h.degree(v) === 4;}).length === h.order(), 73 | "Degree of all vertices is 4."); 74 | test.done(); 75 | }, 76 | 77 | 'Perterson graph': function (test) 78 | { 79 | var g = Graph.peterson(); 80 | 81 | test.ok(g.order() === 10, 82 | "Peterson graph has 10 vertices."); 83 | test.ok(g.size() === 15, 84 | "Peterson graph has 15 edges."); 85 | test.done(); 86 | }, 87 | 88 | 'Bipartite double cover': function (test) 89 | { 90 | var g = Graph.peterson().bipartite_double_cover(); 91 | 92 | test.ok(g.order() === 20, 93 | "Desargues graph has 20 vertices.") 94 | test.ok(g.size() === 30, 95 | "Desargues graph has 30 vertices.") 96 | test.done(); 97 | }, 98 | 99 | 'Complete graphs': function (test) 100 | { 101 | for (var n = 2; n < 5; n++) 102 | { 103 | var g = Graph.k(n); 104 | var size = n*(n-1)/2; 105 | test.ok(g.order() === n, 106 | "K("+n+") has "+n+" vertices."); 107 | test.ok(g.size() === size, 108 | "K("+n+") has "+size+" edges."); 109 | } 110 | test.done(); 111 | }, 112 | 113 | 'Cycles': function (test) 114 | { 115 | for (var n = 3; n < 6; n++) 116 | { 117 | var g = Graph.c(n); 118 | test.ok(g.order() === n, 119 | "C("+n+") has "+n+" vertices."); 120 | test.ok(g.size() === n, 121 | "C("+n+") has "+n+" edges."); 122 | test.ok(g.grep(function (v) {return (v+1)%n in g.adj(v);}).length === n, 123 | "C("+n+") neighbors are adjacent."); 124 | } 125 | test.done(); 126 | }, 127 | 128 | 'Union': function (test) 129 | { 130 | var g = new Graph({a: ['b', 'c']}); 131 | var h = new Graph({b: ['c']}); 132 | var gh = g.union(h); 133 | 134 | test.ok(gh.get('a', 'b') && gh.get('a', 'c') && gh.get('b', 'c'), 135 | "All edges exist."); 136 | test.ok(gh.order() === 3, 137 | "Order is 3."); 138 | test.ok(gh.size() === 3, 139 | "Size is 3."); 140 | test.done(); 141 | }, 142 | 143 | 'Bipartite testing': function (test) 144 | { 145 | test.ok(Graph.k(2).is_bipartite(), 146 | "K(2) is bipartite."); 147 | 148 | test.ok(!Graph.k(3).is_bipartite(), 149 | "K(3) is not bipartite."); 150 | 151 | var k2k3 = Graph.k(2, ['a', 'b']).union(Graph.k(3, ['c', 'd', 'e'])); 152 | test.ok(!k2k3.is_bipartite(), 153 | "K(2) union K(3) is not bipartite."); 154 | 155 | test.ok(Graph.peterson().bipartite_double_cover().is_bipartite(), 156 | "Desargues graph is bipartite."); 157 | test.done(); 158 | }, 159 | 160 | 'Completeness testing': function (test) 161 | { 162 | test.ok(!new Graph({a: ['a'], b: ['b']}).is_complete(), 163 | "Graph with two loops is not complete."); 164 | 165 | test.ok(new Graph().is_complete(), 166 | "Null graph is complete."); 167 | 168 | test.ok(new Graph({a: ['b', 'c'], b: ['c']}).is_complete(), 169 | "K(3) is complete."); 170 | test.done(); 171 | }, 172 | 173 | 'Cycle testing': function (test) 174 | { 175 | test.ok(!new Graph({a: ['b'], b: ['c']}).is_cycle(), 176 | "Path on a, b, c is not a cycle."); 177 | 178 | test.ok(Graph.c(3).is_cycle(), 179 | "C(3) is a cycle."); 180 | 181 | test.ok(!Graph.c(3, ['a', 'b', 'c']).union(Graph.c(4, ['d', 'e', 'f', 'g'])).is_cycle(), 182 | "C(3) union C(4) is not a cycle.") 183 | test.done(); 184 | }, 185 | 186 | 'Subgraph of C(4)': function (test) 187 | { 188 | var g = Graph.c(4, ['a', 'b', 'c', 'd']).subgraph(['a', 'b', 'c']); 189 | 190 | test.ok(!g.is_cycle(), 191 | "Not a cycle.") 192 | 193 | test.ok(g.order() === 3, 194 | "Has 3 vertices."); 195 | 196 | test.ok(g.size() === 2, 197 | "Has 2 edges."); 198 | test.done(); 199 | }, 200 | 201 | 'Subgraph of K(4)': function (test) 202 | { 203 | var g = Graph.k(4, ['a', 'b', 'c', 'd']).subgraph(['a', 'b', 'c']); 204 | 205 | test.ok(g.order() === 3, 206 | "Has 3 vertices."); 207 | 208 | test.ok(g.is_complete(), 209 | "Is K(3)."); 210 | test.done(); 211 | }, 212 | 213 | 'Subgraph testing': function (test) 214 | { 215 | test.ok(Graph.c(4).is_subgraph(Graph.k(4)), 216 | "C(4) is subgraph of K(4)"); 217 | 218 | test.ok(!new Graph({a: ['b', 'c']}).is_subgraph(new Graph({b: ['a', 'c']})), 219 | "Subgraph negative."); 220 | test.done(); 221 | }, 222 | 223 | 'Subgraph vertices': function (test) 224 | { 225 | var a = new Date("1/1/2001"); 226 | var b = new Date("2/2/2002"); 227 | 228 | var g = new Graph(); 229 | g.set(a, b); 230 | g.each(function (v) 231 | { 232 | test.ok(v === a || v === b, 233 | "Vertices of graph match."); 234 | }); 235 | 236 | var h = g.subgraph([a, b]); 237 | h.each(function (v) 238 | { 239 | test.ok(v === a || v === b, 240 | "Vertices of subgraph match."); 241 | }); 242 | test.done(); 243 | }, 244 | }; 245 | --------------------------------------------------------------------------------