├── test ├── mocha.opts ├── module-under-test.js ├── test-jsgraph.js ├── test-digraph-algorithms.js ├── test-module-resolver.js ├── test-module-exports.js ├── fixture │ ├── test-runner-create-digraph.js │ ├── test-runner-algorithm-common-request.js │ ├── test-runner-digraph-in-params.js │ ├── test-runner-algorithm-common-visitor.js │ ├── test-runner-digraph-algorithm-dft.js │ ├── test-runner-digraph-algorithm-bft.js │ ├── dfv-results-recorder.js │ └── bfv-results-recorder.js ├── test-digraph-algorithm-common-context.js ├── test-digraph-algorithm-common-visitor.js ├── test-digraph-algorithm-transpose.js ├── test-digraph-container-create.js ├── test-digraph-in-param-validators.js ├── test-digraph-algorithm-common-request.js ├── test-digraph-algorithm-dft.js └── test-digraph-container.js ├── .travis.yml ├── .npmignore ├── src ├── arc_core_digraph_algorithm_colors.js ├── arc_core_digraph_export.js ├── arc_core_digraph_algorithm_visit.js ├── arc_core_digraph_algorithm_transpose.js ├── arc_core_digraph_algorithm_context.js ├── arc_core_graph_util.js ├── arc_core_graph.js ├── arc_core_digraph_in_params.js ├── arc_core_digraph_import.js ├── arc_core_digraph_algorithm_request.js ├── arc_core_digraph_algorithm_bft.js ├── arc_core_digraph_algorithm_dft.js └── arc_core_digraph.js ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── examples └── bft-vertex-ranking.js ├── package.json └── README.md /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --check-leaks 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.10" 4 | - "7.9" 5 | -------------------------------------------------------------------------------- /test/module-under-test.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./test-module-resolver')('arccore'); 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .travis.yml 3 | .npmignore 4 | src/*~ 5 | docs/ 6 | examples/ 7 | Gruntfile.js 8 | test/ 9 | -------------------------------------------------------------------------------- /test/test-jsgraph.js: -------------------------------------------------------------------------------- 1 | // Top-level Mocha test file for encapsule/jsgraph module. 2 | 3 | require('./test-digraph-in-param-validators'); 4 | require('./test-digraph-container'); 5 | require('./test-digraph-container-create'); 6 | require('./test-digraph-algorithms'); 7 | require('./test-module-exports'); 8 | 9 | -------------------------------------------------------------------------------- /test/test-digraph-algorithms.js: -------------------------------------------------------------------------------- 1 | // Encapsule/jsgraph/test/test-jsgraph-algorithms.js 2 | 3 | require('./test-digraph-algorithm-transpose'); 4 | require('./test-digraph-algorithm-common-context'); 5 | require('./test-digraph-algorithm-common-request'); 6 | require('./test-digraph-algorithm-common-visitor'); 7 | require('./test-digraph-algorithm-bft'); 8 | require('./test-digraph-algorithm-dft'); 9 | 10 | -------------------------------------------------------------------------------- /src/arc_core_digraph_algorithm_colors.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | 12 | // Encapsule/jsgraph/src/digraph-color.js 13 | // 14 | 15 | // Color ordinals used by directed graph algorithms. 16 | 17 | module.exports = { 18 | white: 0, 19 | gray: 1, 20 | black: 2 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | *~ 28 | \#* 29 | -------------------------------------------------------------------------------- /test/test-module-resolver.js: -------------------------------------------------------------------------------- 1 | // test-module-resolver.js 2 | // 3 | // A little trick to decouple Mocha tests from the location 4 | // of the modules they're testing. 5 | // 6 | 7 | var PATH = require('path'); 8 | 9 | // packageName_ is the npm package name used to publish ARCspace/arc* 10 | // repositories on github and npmjs.org. Note that these names are used 11 | // here to select a subdirectory in the ./BUILD tree to use as the basis 12 | // for further manual selecton of the target require. 13 | 14 | module.exports = function(packageName_) { 15 | 16 | basedir = "../src/"; 17 | return function(submoduleName_) { 18 | var submodulePath = PATH.join(basedir, submoduleName_); 19 | console.log("> loading module under test '" + submodulePath + "'..."); 20 | return require(submodulePath); 21 | }; 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // encapsule/jsgraph/Gruntfile.js 2 | 3 | module.exports = function (grunt_) { 4 | 5 | var configObject = { 6 | pkg: grunt_.file.readJSON("package.json"), 7 | 8 | jshint: { 9 | files: [ 'Gruntfile.js', 'library.js', 'src/*.js', 'test/*.js' ], 10 | options: { 11 | } 12 | }, 13 | 14 | // Execute server-side Mocha tests using the grunt-mocha-test module. 15 | 16 | mochaTest: { 17 | options: { reporter: 'spec', checkLeaks: true }, 18 | src: [ 'test/test-jsgraph.js' ] 19 | } 20 | }; 21 | 22 | grunt_.initConfig(configObject); 23 | 24 | grunt_.loadNpmTasks("grunt-contrib-clean"); 25 | grunt_.loadNpmTasks("grunt-contrib-jshint"); 26 | grunt_.loadNpmTasks("grunt-mocha-test"); 27 | 28 | grunt_.registerTask("lint", [ "jshint" ]); 29 | grunt_.registerTask("test", [ "mochaTest" ]); 30 | grunt_.registerTask("default", [ "lint", "test" ]); 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2017 Christopher D. Russell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/bft-vertex-ranking.js: -------------------------------------------------------------------------------- 1 | // Encapsule/jsgraph/examples/bft-vertex-ranking.js 2 | 3 | var jsgraph = require('../index'); // in-package example 4 | 5 | var response = jsgraph.directed.create({ 6 | elist: [ 7 | { e: { u: "A", v: "B" } }, 8 | { e: { u: "B", v: "C" } }, 9 | { e: { u: "B", v: "D" } } 10 | ] 11 | }); 12 | if (response.error) { 13 | throw new Error(response.error); 14 | } 15 | 16 | var digraph = response.result; 17 | 18 | 19 | // VERTEX RANKING (i.e. distance from start vertex) 20 | var vertexRankHashtable = {}; 21 | var bftVisitorInterface = { 22 | startVertex: function(request) { 23 | request.g.setVertexProperty({ u: request.u, p: 0}); 24 | return true; 25 | }, 26 | treeEdge: function (request) { 27 | request.g.setVertexProperty({ u: request.e.v, p: request.g.getVertexProperty(request.e.u) + 1}); 28 | return true; 29 | } 30 | }; 31 | 32 | // ACTUATE OUR VISITOR INTERFACE WITH BFT TO PRODUCE THE RESULT 33 | response = jsgraph.directed.breadthFirstTraverse({ 34 | digraph: digraph, 35 | visitor: bftVisitorInterface 36 | }); 37 | if (response.error) { 38 | throw new Error(response.error); 39 | } 40 | 41 | console.log("DirectedGraph: '" + digraph.stringify(undefined, 4) + "'"); 42 | console.log("BFT traversal: '" + JSON.stringify(response.result,undefined,4) + "'"); 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/test-module-exports.js: -------------------------------------------------------------------------------- 1 | 2 | var chai = require('chai'); 3 | var assert = chai.assert; 4 | 5 | var testModule = require('./module-under-test'); 6 | var jsgraph = testModule('arc_core_graph'); 7 | var DirectedGraph = testModule('arc_core_digraph'); 8 | 9 | describe("Module export tests", function() { 10 | 11 | describe("Directed graph container API", function() { 12 | 13 | it("module export 'jsgraph.directed' should be an object", function() { 14 | assert.isObject(jsgraph.directed); 15 | }); 16 | 17 | it("module export 'jsgraph.directed.colors' should be an object", function() { 18 | assert.isObject(jsgraph.directed.colors); 19 | }); 20 | 21 | it("module export 'jsgraph.directed.create' should be a function", function() { 22 | assert.isFunction(jsgraph.directed.create); 23 | }); 24 | 25 | it("module export 'jsgraph.directed.transpose' should be a function", function() { 26 | assert.isFunction(jsgraph.directed.transpose); 27 | }); 28 | 29 | it("module export 'jsgraph.directed.breadthFirstTraverse' should be a function", function() { 30 | assert.isFunction(jsgraph.directed.breadthFirstTraverse); 31 | }); 32 | 33 | it("module export 'jsgraph.directed.depthFirstTraverse' should be a function", function() { 34 | assert.isFunction(jsgraph.directed.depthFirstTraverse); 35 | }); 36 | 37 | it("module export 'jsgraph.directed.createTraveralContext' should be a function", function() { 38 | assert.isFunction(jsgraph.directed.createTraversalContext); 39 | }); 40 | 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsgraph", 3 | "version": "0.7.1", 4 | "description": "DirectedGraph container class + BFT/DFT/transpose algorithms inspired by Boost C++ Graph Library API.", 5 | "main": "src/arc_core_graph.js", 6 | "scripts": { 7 | "test": "mocha -R spec ./test/test-jsgraph.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/Encapsule/jsgraph.git" 12 | }, 13 | "keywords": [ 14 | "graph", 15 | "vertex", 16 | "vertices", 17 | "edge", 18 | "edges", 19 | "node", 20 | "nodes", 21 | "link", 22 | "algorithm", 23 | "depth-first search", 24 | "breadth-first search", 25 | "BFS", 26 | "DFS", 27 | "data modeling", 28 | "data", 29 | "JSON", 30 | "data semantics", 31 | "semantic data", 32 | "container", 33 | "dependency", 34 | "topological", 35 | "transpose", 36 | "filter", 37 | "transform", 38 | "route", 39 | "model", 40 | "classify", 41 | "classification", 42 | "analysis", 43 | "sorting", 44 | "in-memory", 45 | "database", 46 | "relational", 47 | "hyper-relational", 48 | "design pattern", 49 | "visit", 50 | "visitor" 51 | ], 52 | "author": "ChrisRus", 53 | "license": "MIT", 54 | "bugs": { 55 | "url": "https://github.com/Encapsule/jsgraph/issues" 56 | }, 57 | "homepage": "https://github.com/Encapsule/jsgraph", 58 | "devDependencies": { 59 | "grunt": "^0.4.5", 60 | "grunt-contrib-clean": "^0.6.0", 61 | "grunt-mocha-test": "^0.12.7", 62 | "chai": "^1.10.0", 63 | "grunt-contrib-jshint": "~0.10.0", 64 | "mocha": "^2.1.0", 65 | "node-uuid": "^1.4.2" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/arc_core_digraph_export.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | // Export the topology and attached vertex and edge properties 12 | // of a DirectedGraph container object as a JSON-format UTF8 13 | // string. This canonical format can be passed as an optional 14 | // constructor parameter to restore container state across 15 | // execution contexts. 16 | 17 | var helperFunctions = require('./arc_core_graph_util'); 18 | var DigraphDataExporter = module.exports = {}; 19 | 20 | DigraphDataExporter.exportObject = function (digraph_) { 21 | var digraphState = { 22 | name: digraph_.getGraphName(), 23 | description: digraph_.getGraphDescription(), 24 | vlist: [], 25 | elist: [] 26 | }; 27 | var vertexSerialized = {}; // Keep track of the vertices referenced in the edge list. 28 | var edgeList = digraph_.getEdges(); 29 | var vertexList = digraph_.getVertices(); 30 | digraph_.getEdges().forEach(function(edge_) { 31 | var edgeProperty = digraph_.getEdgeProperty(edge_); 32 | digraphState.elist.push({ e: edge_, p: edgeProperty }); 33 | vertexSerialized[edge_.u] = vertexSerialized[edge_.v] = true; 34 | }); 35 | digraph_.getVertices().forEach(function(vertexId_) { 36 | var vertexProperty = digraph_.getVertexProperty(vertexId_); 37 | var jstype = helperFunctions.JSType(vertexProperty); 38 | // If the vertex has an attached property, serialize it to the vlist. 39 | if (jstype !== '[object Undefined]') { 40 | digraphState.vlist.push({ u: vertexId_, p: vertexProperty }); 41 | } else { 42 | // If the vertex wasn't mentioned in the elist, we need to serialize, sans property, to the vlist. 43 | if (vertexSerialized[vertexId_] !== true) { 44 | digraphState.vlist.push({ u: vertexId_ }); 45 | } 46 | } 47 | }); 48 | return digraphState; 49 | }; 50 | 51 | DigraphDataExporter.exportJSON = function (digraph_, replacer_, space_) { 52 | return JSON.stringify(DigraphDataExporter.exportObject(digraph_), replacer_, space_); 53 | }; 54 | -------------------------------------------------------------------------------- /src/arc_core_digraph_algorithm_visit.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | // Wraps call to DirectedGraph algorithm visitor function callbacks. 12 | 13 | var helperFunctions = require('./arc_core_graph_util'); 14 | 15 | /* 16 | request = { 17 | visitor: interface object reference 18 | algorithm: string name of the algorithm for error messages 19 | method: string name of the visitor method to call 20 | request: request object to pass to the visitor method 21 | }, 22 | response = { 23 | error: null or string explaining by response.error is null 24 | result: null (error) or Boolean flag set true to continue search 25 | } 26 | */ 27 | 28 | module.exports = function (request_) { 29 | 30 | var response = { error: null, result: null }; 31 | var errors = []; 32 | var inBreakScope = false; 33 | while (!inBreakScope) { 34 | inBreakScope = true; 35 | var visitorCallback = request_.visitor[request_.method]; 36 | var jstype = helperFunctions.JSType(visitorCallback); 37 | // If the visitor function is not defined on the visitor object, return true to continue the search. 38 | if (jstype !== '[object Function]') { 39 | if (jstype !== '[object Undefined]') { 40 | errors.unshift(request_.algorithm + " visitor interface method '" + request_.method + "' is type '" + jstype + "' instead of '[object Function]' as expected."); 41 | break; 42 | } 43 | response.result = true; 44 | break; 45 | } 46 | var continueSearch = visitorCallback(request_.request); 47 | jstype = helperFunctions.JSType(continueSearch); 48 | if (jstype !== '[object Boolean]') { 49 | errors.unshift(request_.algorithm + " visitor interface error in callback function '" + request_.method + "'. Function returned type '" + jstype + "' instead of expected '[object Boolean]'."); 50 | break; 51 | } 52 | response.result = continueSearch; 53 | } 54 | if (errors.length) { 55 | response.error = errors.join(' '); 56 | } 57 | return response; 58 | }; 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/arc_core_digraph_algorithm_transpose.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | // transposeDirectedGraph computes the transpose of input digraph_, 12 | // returns the the result as a new DirectedGraph instance. 13 | // More info on directed graph transposition: 14 | // http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/transpose_graph.html 15 | 16 | var helperFunctions = require('./arc_core_graph_util'); 17 | var DirectedGraph = require('./arc_core_digraph').DirectedGraph; 18 | 19 | /* 20 | request = DirectedGraph reference 21 | response = { 22 | error: null or string explaining why result is null 23 | result: a new DirectedGraph instance with the same vertex set, 24 | the edge set transposed (direction reversed), and vertex 25 | and edge properties (if any) linked by reference to the 26 | source digraph 27 | } 28 | */ 29 | 30 | module.exports = function (digraph_) { 31 | var response = { error: null, result: null }; 32 | var errors = []; 33 | var innerResponse; 34 | 35 | var digraphOut = new DirectedGraph(); 36 | 37 | var jstype = helperFunctions.JSType(digraph_); 38 | if (jstype !== '[object Object]') { 39 | errors.unshift("Expected reference to DirectedGraph but found type '" + jstype + "'."); 40 | } else { 41 | 42 | digraph_.getVertices().forEach(function(vertexId_) { 43 | innerResponse = digraphOut.addVertex({ u: vertexId_, p: digraph_.getVertexProperty(vertexId_) }); 44 | if (innerResponse.error) { 45 | errors.unshift(innerResponse.error); 46 | } 47 | }); 48 | 49 | digraph_.getEdges().forEach(function(edge_) { 50 | innerResponse = digraphOut.addEdge({ e: { u: edge_.v, v: edge_.u }, p: digraph_.getEdgeProperty(edge_) }); 51 | if (innerResponse.error) { 52 | errors.unshift(innerResponse.error); 53 | } 54 | }); 55 | 56 | } // end else 57 | 58 | if (errors.length) { 59 | errors.unshift("jsgraph.directed.transpose failed:"); 60 | response.error = errors.join(' '); 61 | } else { 62 | response.result = digraphOut; 63 | } 64 | return response; 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /src/arc_core_digraph_algorithm_context.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | var helperFunctions = require('./arc_core_graph_util'); 12 | var colors = require('./arc_core_digraph_algorithm_colors'); 13 | 14 | module.exports = function (request_) { 15 | var response = { error: null, result: null }; 16 | var errors = []; 17 | var traverseContext = { searchStatus: 'pending', colorMap: {}, undiscoveredMap: {} }; 18 | var initializeColorMapRecord = function (vertexId_) { 19 | traverseContext.colorMap[vertexId_] = colors.white; 20 | traverseContext.undiscoveredMap[vertexId_] = true; 21 | }; 22 | var inBreakScope = false; 23 | while (!inBreakScope) { 24 | inBreakScope = true; 25 | var objectTS = '[object Object]'; 26 | // Verify request. 27 | var type = helperFunctions.JSType(request_); 28 | if (type !== objectTS) { 29 | errors.unshift("Expected request to be of type '" + objectTS + "' but found '" + type + "'."); 30 | break; 31 | } 32 | // Verify request.digraph. 33 | type = helperFunctions.JSType(request_.digraph); 34 | if (type !== objectTS) { 35 | errors.unshift("Expected request.digraph to be of type '" + objectTS + "' but found '" + type + "'."); 36 | break; 37 | } 38 | // Initialize the BFS search context object. 39 | request_.digraph.getVertices().forEach(initializeColorMapRecord); 40 | // Assign the result. Note that it's incumbant upon the first invocation of 41 | // traversal algorithm to check/set the traverseContext.searchStatus flag and 42 | // correctly call the visitor.initializeVertex callback on each vertex in the 43 | // color map prior to the start of the search. traverseContext.searchStatus should 44 | // be 'running' while a search is in progress. 'terminated' if prematurely terminated 45 | // by client visitor code. 'complete' when search concludes normally. 46 | response.result = traverseContext; 47 | } 48 | if (errors.length) { 49 | errors.unshift("jsgraph.directed.createTraverseContext failed:"); 50 | response.error = errors.join(' '); 51 | } 52 | return response; 53 | }; 54 | -------------------------------------------------------------------------------- /test/fixture/test-runner-create-digraph.js: -------------------------------------------------------------------------------- 1 | // test-runner-create-digraph.js 2 | 3 | /* 4 | request = { 5 | testName: string 6 | validConfig: boolean, 7 | request: object 8 | expectedResults: { 9 | error: 10 | result: 11 | } 12 | */ 13 | 14 | var assert = require('chai').assert; 15 | var testModule = require('../module-under-test'); 16 | var DirectedGraphContainer = testModule('arc_core_digraph'); 17 | var createDirectedGraph = DirectedGraphContainer.createDirectedGraph; 18 | var DirectedGraph = DirectedGraphContainer.DirectedGraph; 19 | assert.isDefined(createDirectedGraph); 20 | assert.isNotNull(createDirectedGraph); 21 | assert.isFunction(createDirectedGraph); 22 | 23 | module.exports = function (testVector_) { 24 | 25 | var testName = "jsgraph.createDirectedGraph test: " + testVector_.testName; 26 | 27 | describe(testName, function() { 28 | 29 | var response = null; 30 | 31 | before(function() { 32 | var testCreateDirectedGraph = function() { 33 | response = createDirectedGraph(testVector_.request); 34 | }; 35 | assert.doesNotThrow(testCreateDirectedGraph, "jsgraph.createDirectedGraph SHOULD NEVER THROW!"); 36 | }); 37 | 38 | it("the call should have returned a response object.", function() { 39 | assert.isNotNull(response); 40 | assert.isObject(response); 41 | assert.property(response, 'error'); 42 | assert.property(response, 'result'); 43 | }); 44 | 45 | if (testVector_.validConfig) { 46 | 47 | it("the call response should not indicate an error.", function() { 48 | assert.isNull(response.error); 49 | }); 50 | 51 | it("the call response should have returned a DirectedGraph container object.", function() { 52 | assert.isNotNull(response.result); 53 | assert.instanceOf(response.result, DirectedGraph); 54 | }); 55 | 56 | it("the returned graph JSON should match expected control value.", function() { 57 | assert.instanceOf(response.result, DirectedGraph); 58 | assert.equal(response.result.stringify(), testVector_.expectedResults.result); 59 | }); 60 | 61 | } else { 62 | 63 | it("the call response should not have returned a result.", function() { 64 | assert.isNull(response.result); 65 | }); 66 | 67 | it("the call response error should match the expected error string.", function() { 68 | assert.equal(response.error, testVector_.expectedResults.error); 69 | }); 70 | 71 | } 72 | 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /src/arc_core_graph_util.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | var JSType = function(reference_) { 12 | return Object.prototype.toString.call(reference_); 13 | }; 14 | 15 | /* 16 | request = { 17 | ref: reference 18 | types: string or array of strings (e.g. '[object Object]') 19 | } 20 | response = { 21 | error: null (success) or string (error) 22 | result: null (failure) or JSType of request.ref (success) 23 | } 24 | 25 | */ 26 | var JSTypeInTypeSet = function(request_) { 27 | var response = { error: null, result: null }; 28 | var errors = []; 29 | var allowedTypeSet = {}; 30 | var jstype = JSType(request_.types); 31 | switch (jstype) { 32 | case '[object String]': 33 | allowedTypeSet = [ request_.types ]; 34 | break; 35 | case '[object Array]': 36 | allowedTypeSet = request_.types; 37 | break; 38 | default: 39 | errors.unshift("Invalid request.types value type '" + jstype + "'. Expected either '[object String]' or '[object Array]'."); 40 | break; 41 | } 42 | jstype = JSType(request_.description); 43 | if (jstype !== '[object String]') { 44 | errors.unshift("Invalid request.description value type '" + jstype + "'. Expected '[object String]'."); 45 | } 46 | if (!errors.length) { 47 | jsType = JSType(request_.ref); 48 | response.result = (allowedTypeSet.indexOf(jsType) !== -1) && jsType || null; 49 | } 50 | if (!response.result) { 51 | response.guidance = request_.description + " value type '" + jstype + "' is invalid. Expected one of [" + allowedTypeSet.join(',') + "]."; 52 | } 53 | if (errors.length) { 54 | errors.unshift("JSTypeInTypeSet failed:"); 55 | response.error = errors.join(' '); 56 | } 57 | return response; 58 | 59 | }; 60 | 61 | var setPropertyValueIfUndefined = function(reference_, propertyName_, valueOrFunction_) { 62 | var type = JSType(reference_[propertyName_]); 63 | if (type === '[object Undefined]') { 64 | type = JSType(valueOrFunction_); 65 | if (type !== '[object Function]') { 66 | reference_[propertyName_] = valueOrFunction_; 67 | } else { 68 | reference_[propertyName_] = valueOrFunction_(); 69 | } 70 | return true; 71 | } 72 | return false; 73 | }; 74 | 75 | module.exports = { 76 | JSType: JSType, 77 | setPropertyValueIfUndefined: setPropertyValueIfUndefined 78 | }; 79 | -------------------------------------------------------------------------------- /test/test-digraph-algorithm-common-context.js: -------------------------------------------------------------------------------- 1 | // test-digraph-algorithm-common-context.js 2 | 3 | // external 4 | var assert = require('chai').assert; 5 | 6 | // internal 7 | var testModule = require('./module-under-test'); 8 | var DirectedGraph = testModule('arc_core_digraph').DirectedGraph; 9 | var createTraverseContext = testModule('arc_core_digraph_algorithm_context'); 10 | 11 | 12 | describe("Traverse context: missing request object.", function() { 13 | var response = null; 14 | 15 | before(function() { 16 | response = createTraverseContext(); 17 | }); 18 | it("createTraverseContext call should have returned a response object", function() { 19 | assert.isDefined(response); 20 | assert.isNotNull(response); 21 | assert.isObject(response); 22 | }); 23 | it("response JSON should match expected result", function() { 24 | var expectedResult = '{"error":"jsgraph.directed.createTraverseContext failed: Expected request to be of type \'[object Object]\' but found \'[object Undefined]\'.","result":null}'; 25 | assert.equal(JSON.stringify(response), expectedResult); 26 | }); 27 | }); 28 | 29 | describe("Traverse context: missing digraph object.", function() { 30 | var response = null; 31 | 32 | before(function() { 33 | response = createTraverseContext({}); 34 | }); 35 | it("createTraverseContext call should have returned a response object", function() { 36 | assert.isDefined(response); 37 | assert.isNotNull(response); 38 | assert.isObject(response); 39 | }); 40 | it("response JSON should match expected result", function() { 41 | var expectedResult = '{"error":"jsgraph.directed.createTraverseContext failed: Expected request.digraph to be of type \'[object Object]\' but found \'[object Undefined]\'.","result":null}'; 42 | assert.equal(JSON.stringify(response), expectedResult); 43 | }); 44 | }); 45 | 46 | 47 | describe("Traverse context: constructed for a specific digraph", function() { 48 | 49 | var digraph = null; 50 | var response = null; 51 | var searchResults = null; 52 | var bfvContext = null; 53 | 54 | before(function() { 55 | digraph = new DirectedGraph(); 56 | digraph.addVertex({ u: 'island'}); 57 | response = createTraverseContext({ digraph: digraph }); 58 | }); 59 | 60 | it("the call to createTraverseContext should have returned a response.", function() { 61 | assert.isDefined(response); 62 | assert.isNotNull(response); 63 | assert.isObject(response); 64 | }); 65 | 66 | it("response JSON should match expected result", function() { 67 | var expectedResult = '{"error":null,"result":{"searchStatus":"pending","colorMap":{"island":0},"undiscoveredMap":{"island":true}}}'; 68 | assert.equal(JSON.stringify(response), expectedResult); 69 | }); 70 | 71 | }); 72 | 73 | 74 | -------------------------------------------------------------------------------- /test/fixture/test-runner-algorithm-common-request.js: -------------------------------------------------------------------------------- 1 | // test-runner-digraph-algorithm-bft-request.js 2 | // 3 | 4 | var assert = require('chai').assert; 5 | 6 | var testModule = require('../module-under-test'); 7 | var normalizeAlgorithmRequest = testModule('arc_core_digraph_algorithm_request'); 8 | 9 | /* 10 | request = { 11 | testName: string, 12 | validConfig: boolean, 13 | request: object, 14 | expectedResults: { 15 | error: string, 16 | json: string 17 | } 18 | } 19 | */ 20 | 21 | module.exports = function (testVector_) { 22 | 23 | var testName = "xFT request normalizer unit test: " + testVector_.testName + ":"; 24 | 25 | describe(testName, function() { 26 | var parseResponse = null; 27 | before(function() { 28 | var testNormalizeXFTRequest = function() { 29 | parseResponse = normalizeAlgorithmRequest(testVector_.request); 30 | }; 31 | assert.doesNotThrow(testNormalizeXFTRequest, "xFT REQUEST NORMALIZER SHOULD NEVER THROW!"); 32 | }); 33 | it("The xFT request normalizer should have returned a response object.", function() { 34 | assert.isDefined(parseResponse); 35 | assert.isNotNull(parseResponse); 36 | assert.isObject(parseResponse); 37 | }); 38 | if (testVector_.validConfig) { 39 | describe("The test asserts the request to be valid. Verify the response contains the expected result.", function() { 40 | it("The request normalizer should not have returned an error.", function() { 41 | assert.isNull(parseResponse.error); 42 | }); 43 | it("The request normalizer should have returned a result.", function() { 44 | assert.isNotNull(parseResponse.result); 45 | assert.isObject(parseResponse.result); 46 | }); 47 | it("The result JSON should match the expected control value.", function() { 48 | assert.equal(JSON.stringify(parseResponse.result), testVector_.expectedResults.json); 49 | }); 50 | }); 51 | } else { 52 | describe("The test asserts the request to be invalid. Verify the response contains the expected error.", function() { 53 | it("The request normalizer should not have returned a result.", function() { 54 | assert.isNull(parseResponse.result); 55 | }); 56 | it("The request normalizer should have returned an error string.", function() { 57 | assert.isNotNull(parseResponse.error); 58 | assert.isString(parseResponse.error); 59 | }); 60 | it("The error string should match the expected control value.", function() { 61 | assert.equal(parseResponse.error, testVector_.expectedResults.error); 62 | }); 63 | }); 64 | } 65 | 66 | }); 67 | 68 | }; 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/fixture/test-runner-digraph-in-params.js: -------------------------------------------------------------------------------- 1 | // test-runner-digraph-in-params.js 2 | // 3 | 4 | var assert = require('chai').assert; 5 | 6 | var testModule = require('../module-under-test'); 7 | var digraphInParams = testModule('arc_core_digraph_in_params'); 8 | 9 | /* 10 | request = { 11 | testName: string, 12 | validatorFunction: string, // method name in digraphInParams export object 13 | validConfig: boolean, 14 | request: object, 15 | expectedResults: { 16 | error: string, 17 | result: string 18 | } 19 | } 20 | */ 21 | 22 | module.exports = function (testVector_) { 23 | 24 | var testName = "DirectedGraph in-parameter validator `" + testVector_.validatorFunction + "' test: " + testVector_.testName + ":"; 25 | 26 | describe(testName, function() { 27 | var response = null; 28 | before(function() { 29 | var testValidator = function() { 30 | response = digraphInParams[testVector_.validatorFunction](testVector_.request); 31 | }; 32 | assert.doesNotThrow(testValidator, "DirectedGraph in-parameter validator '" + testVector_.validatorFunction + "' SHOULD NEVER THROW!"); 33 | }); 34 | it("The '" + testVector_.validatorFunction + "' function should have returned a response object.", function() { 35 | assert.isDefined(response); 36 | assert.isNotNull(response); 37 | assert.isObject(response); 38 | }); 39 | if (testVector_.validConfig) { 40 | describe("The test asserts the request to be valid. Verify the response contains the expected result.", function() { 41 | it("The request normalizer should not have returned an error.", function() { 42 | assert.isNull(response.error); 43 | }); 44 | it("The request normalizer should have returned a result.", function() { 45 | assert.isNotNull(response.result); 46 | assert.isBoolean(response.result); 47 | }); 48 | it("The result JSON should match the expected control value.", function() { 49 | assert.equal(response.result, testVector_.expectedResults.result); 50 | }); 51 | }); 52 | } else { 53 | describe("The test asserts the request to be invalid. Verify the response contains the expected error.", function() { 54 | it("The request normalizer should not have returned a result.", function() { 55 | assert.isFalse(response.result); 56 | }); 57 | it("The request normalizer should have returned an error string.", function() { 58 | assert.isNotNull(response.error); 59 | assert.isString(response.error); 60 | }); 61 | it("The error string should match the expected control value.", function() { 62 | assert.equal(response.error, testVector_.expectedResults.error); 63 | }); 64 | }); 65 | } 66 | 67 | }); 68 | 69 | }; 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /test/fixture/test-runner-algorithm-common-visitor.js: -------------------------------------------------------------------------------- 1 | // test-runner-algorithm-common-visitor.js 2 | 3 | var assert = require('chai').assert; 4 | 5 | var testModule = require('../module-under-test'); 6 | var callVisitorMethod = testModule('arc_core_digraph_algorithm_visit'); 7 | 8 | /* 9 | request = { 10 | testName: string 11 | validConfig: Boolean 12 | requestObject: { 13 | algorithm: string 14 | visitor: object 15 | method: string 16 | request: object 17 | } 18 | expectedResults: { 19 | error: string 20 | result: variant 21 | } 22 | } 23 | */ 24 | 25 | module.exports = function (testVector_) { 26 | 27 | var testName = "xFT visitor interface function unit test: " + testVector_.testName + ":"; 28 | describe(testName, function() { 29 | var callbackResponse = null; 30 | var callbackCalled = false; 31 | before(function() { 32 | 33 | var userVisitorMethod = testVector_.request.visitor?testVector_.request.visitor[testVector_.request.method]:undefined; 34 | var facade = { 35 | watcher: userVisitorMethod?function(request) { 36 | callbackCalled = true; 37 | return userVisitorMethod(request); 38 | }:(testVector_.facade?testVector_.facade.watcher:undefined) 39 | }; 40 | var newRequest = testVector_.request; 41 | newRequest.visitor = facade; 42 | newRequest.method = 'watcher'; 43 | 44 | var testVisitorInterfaceMethod = function() { 45 | callbackResponse = callVisitorMethod(newRequest); 46 | }; 47 | assert.doesNotThrow(testVisitorInterfaceMethod, "xFT VISITOR METHOD REQUEST SHOULD NEVER THROW!"); 48 | }); 49 | it("xFT visitor method should have returned a response object.", function() { 50 | assert.isDefined(callbackResponse); 51 | assert.isNotNull(callbackResponse); 52 | }); 53 | 54 | if (testVector_.validConfig) { 55 | 56 | it("The call should not have returned an error.", function() { 57 | assert.isNull(callbackResponse.error); 58 | }); 59 | 60 | it("The call should have returned a result.", function() { 61 | assert.isNotNull(callbackResponse.result); 62 | }); 63 | 64 | it("The result should match the expected result.", function() { 65 | assert.equal(callbackResponse.result, testVector_.expectedResults.result); 66 | }); 67 | 68 | it("The flag indicating if a callback was executed should match expected result.", function() { 69 | assert.equal(callbackCalled, testVector_.expectedResults.callbackCalled); 70 | }); 71 | 72 | } else { 73 | 74 | it("The call not have returned a result.", function() { 75 | assert.isNull(callbackResponse.result); 76 | }); 77 | 78 | it("The call should have returned an error.", function() { 79 | assert.isNotNull(callbackResponse.error); 80 | assert.isString(callbackResponse.error); 81 | }); 82 | 83 | it("The error should match the expected error string.", function() { 84 | assert.equal(callbackResponse.error, testVector_.expectedResults.error); 85 | }); 86 | 87 | } 88 | 89 | }); 90 | }; 91 | -------------------------------------------------------------------------------- /src/arc_core_graph.js: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------- 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2014-2016 Christopher D. Russell 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | This library is part of the Encapsule Project System in Cloud (SiC) open service 26 | architecture. Please follow https://twitter.com/Encapsule for news and updates 27 | about jsgraph and other time saving libraries that do amazing things with in-memory 28 | data on Node.js and HTML. https://github.com/encapsule http://blog.encapsule.org 29 | 30 | -------------------------------------------------------------------------- */ 31 | 32 | var jsgraph = module.exports = { 33 | 34 | // Directed graph support 35 | directed: { 36 | 37 | //// 38 | // Create a DirectedGraph container object. 39 | // 40 | // var response = jsgraph.directed.create(request); 41 | // 42 | // request = Undefined, JSON string, or data object [1] 43 | // 44 | // response = { 45 | // error: null or string explaining why result is null 46 | // result: DirectedGraph container object or null if error 47 | // } 48 | // 49 | // Notes: 50 | // 51 | // [1] see DirectedGraph.toJSON/toObject methods. 52 | // 53 | //// 54 | create: require('./arc_core_digraph').createDirectedGraph, 55 | 56 | // Directed graph transposition algorithm. 57 | // Creates a new DirectedGraph container object that's identical 58 | // to a caller-specified digraph except that the direction of the 59 | // the edges are reverese in the result digraph. Note that if present, 60 | // vertex and edge properties in the source digraph are copied by 61 | // reference to the result digraph. 62 | transpose: require('./arc_core_digraph_algorithm_transpose'), 63 | 64 | // Directed graph breadth-first traversal visitor algorithm. 65 | breadthFirstTraverse: require('./arc_core_digraph_algorithm_bft'), 66 | 67 | // Directed graph depth-first traversal visitor algorithm. 68 | depthFirstTraverse: require('./arc_core_digraph_algorithm_dft'), 69 | 70 | // ADVANCED 71 | 72 | // Color constant hashtable (advanced). 73 | colors: require('./arc_core_digraph_algorithm_colors'), 74 | 75 | // Directed graph traversal context factory (advanced). 76 | createTraversalContext: require('./arc_core_digraph_algorithm_context') 77 | 78 | } 79 | }; 80 | 81 | 82 | -------------------------------------------------------------------------------- /test/fixture/test-runner-digraph-algorithm-dft.js: -------------------------------------------------------------------------------- 1 | // Encapsule/jsgraph/test/fixture/test-runner-digraph-algorithm-dft.js 2 | // 3 | 4 | var assert = require('chai').assert; 5 | 6 | var testModule = require('../module-under-test'); 7 | var DFT = testModule('arc_core_digraph_algorithm_dft'); 8 | var SearchPathRecorder = require('./dfv-results-recorder'); 9 | 10 | /* 11 | request = { 12 | testName: string 13 | validConfig: boolean 14 | request: breadthFirstTraverse request object 15 | expectedResults: { 16 | error: null or string 17 | result: null or JSON 18 | path: null or JSON 19 | } 20 | */ 21 | 22 | module.exports = function (testVector_) { 23 | 24 | var testName = "DFT test case: " + testVector_.testName + ":"; 25 | 26 | describe(testName, function() { 27 | 28 | var searchResponse = null; 29 | var searchPathRecorder = null; 30 | 31 | before(function() { 32 | var visitor; 33 | if ((testVector_.request !== null) && testVector_.request && (testVector_.request.visitor !== null) && testVector_.request.visitor) { 34 | visitor = testVector_.request.visitor; 35 | } 36 | searchPathRecorder = new SearchPathRecorder(visitor); 37 | var dftRequest = testVector_.request; 38 | if (dftRequest) { 39 | dftRequest.visitor = searchPathRecorder.visitorInterface; 40 | } 41 | var executeRequestedDFT = function() { 42 | searchResponse = DFT(dftRequest); 43 | }; 44 | assert.doesNotThrow(executeRequestedDFT); 45 | }); 46 | 47 | it("The call to DFT should have returned a response object.", function() { 48 | assert.isDefined(searchResponse); 49 | assert.isNotNull(searchResponse); 50 | assert.isObject(searchResponse); 51 | assert.property(searchResponse, 'error'); 52 | assert.property(searchResponse, 'result'); 53 | }); 54 | 55 | if (testVector_.validConfig) { 56 | 57 | describe("Test case is expected to pass. analyze the search results and recorded traversal path.", function() { 58 | it("searchResponse.error should be null.", function() { 59 | assert.isNull(searchResponse.error); 60 | }); 61 | 62 | it("searchResponse.result should match expected search response JSON.", function() { 63 | assert.equal(JSON.stringify(searchResponse.result), testVector_.expectedResults.result); 64 | }); 65 | 66 | it("actualSearchPath should match the expected search path JSON.", function() { 67 | assert.equal(searchPathRecorder.toJSON(), testVector_.expectedResults.path); 68 | }); 69 | }); 70 | 71 | } else { 72 | 73 | describe("Test case is expected to fail. ensure the response error string is as expected.", function() { 74 | 75 | it("searchResponse.result should be null.", function() { 76 | assert.isNull(searchResponse.result); 77 | }); 78 | 79 | it("searchResponse.error should be a non-null string.", function() { 80 | assert.isDefined(searchResponse.error); 81 | assert.isNotNull(searchResponse.error); 82 | assert.isString(searchResponse.error); 83 | }); 84 | 85 | it("searchResponse.error string should match expected error string.", function() { 86 | assert.equal(searchResponse.error, testVector_.expectedResults.error); 87 | }); 88 | 89 | }); 90 | 91 | } 92 | 93 | }); 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /test/fixture/test-runner-digraph-algorithm-bft.js: -------------------------------------------------------------------------------- 1 | // Encapsule/jsgraph/test/fixture/test-runner-digraph-algorithm-bft.js 2 | // 3 | 4 | var assert = require('chai').assert; 5 | 6 | var testModule = require('../module-under-test'); 7 | 8 | var BFT = testModule('arc_core_digraph_algorithm_bft'); 9 | var SearchPathRecorder = require('./bfv-results-recorder'); 10 | 11 | /* 12 | request = { 13 | testName: string 14 | validConfig: boolean 15 | request: breadthFirstTraverse request object 16 | expectedResults: { 17 | error: null or string 18 | result: null or JSON 19 | path: null or JSON 20 | } 21 | */ 22 | 23 | module.exports = function (testVector_) { 24 | 25 | var testName = "BFT test case: " + testVector_.testName + ":"; 26 | 27 | describe(testName, function() { 28 | 29 | var searchResponse = null; 30 | var searchPathRecorder = null; 31 | 32 | before(function() { 33 | var visitor; 34 | if ((testVector_.request !== null) && testVector_.request && (testVector_.request.visitor !== null) && testVector_.request.visitor) { 35 | visitor = testVector_.request.visitor; 36 | } 37 | searchPathRecorder = new SearchPathRecorder(visitor); 38 | var bftRequest = testVector_.request; 39 | if (bftRequest) { 40 | bftRequest.visitor = searchPathRecorder.visitorInterface; 41 | } 42 | var executeRequestedBFT = function() { 43 | searchResponse = BFT(bftRequest); 44 | }; 45 | assert.doesNotThrow(executeRequestedBFT); 46 | }); 47 | 48 | it("The call to BFT should have returned a response object.", function() { 49 | assert.isDefined(searchResponse); 50 | assert.isNotNull(searchResponse); 51 | assert.isObject(searchResponse); 52 | assert.property(searchResponse, 'error'); 53 | assert.property(searchResponse, 'result'); 54 | }); 55 | 56 | if (testVector_.validConfig) { 57 | 58 | describe("Test case is expected to pass. analyze the search results and recorded traversal path.", function() { 59 | it("searchResponse.error should be null.", function() { 60 | assert.isNull(searchResponse.error); 61 | }); 62 | 63 | it("searchResponse.result should match expected search response JSON.", function() { 64 | assert.equal(JSON.stringify(searchResponse.result), testVector_.expectedResults.result); 65 | }); 66 | 67 | it("actualSearchPath should match the expected search path JSON.", function() { 68 | assert.equal(searchPathRecorder.toJSON(), testVector_.expectedResults.path); 69 | }); 70 | }); 71 | 72 | } else { 73 | 74 | describe("Test case is expected to fail. ensure the response error string is as expected.", function() { 75 | 76 | it("searchResponse.result should be null.", function() { 77 | assert.isNull(searchResponse.result); 78 | }); 79 | 80 | it("searchResponse.error should be a non-null string.", function() { 81 | assert.isDefined(searchResponse.error); 82 | assert.isNotNull(searchResponse.error); 83 | assert.isString(searchResponse.error); 84 | }); 85 | 86 | it("searchResponse.error string should match expected error string.", function() { 87 | assert.equal(searchResponse.error, testVector_.expectedResults.error); 88 | }); 89 | 90 | }); 91 | 92 | } 93 | 94 | }); 95 | 96 | }; 97 | -------------------------------------------------------------------------------- /test/test-digraph-algorithm-common-visitor.js: -------------------------------------------------------------------------------- 1 | // test-digraph-algorothm-common-visitor.js 2 | 3 | var testModule = require('./module-under-test'); 4 | var DirectedGraph = testModule('arc_core_digraph').DirectedGraph; 5 | var createTraverseContext = testModule('arc_core_digraph_algorithm_context'); 6 | 7 | var assert = require('chai').assert; 8 | var testAlgorithmVisitorCallback = require('./fixture/test-runner-algorithm-common-visitor'); 9 | 10 | 11 | (function() { 12 | testAlgorithmVisitorCallback({ 13 | testName: "Simple callback, no error, returns true.", 14 | validConfig: true, 15 | request: { 16 | algorithm: "TEST", 17 | method: 'foo', 18 | visitor: { 19 | foo: function(request) { 20 | return true; 21 | } 22 | }, 23 | request: "whatever" 24 | }, 25 | expectedResults: { 26 | error: null, 27 | result: true, 28 | callbackCalled: true 29 | } 30 | }); 31 | })(); 32 | 33 | (function() { 34 | testAlgorithmVisitorCallback({ 35 | testName: "Simple callback, no error, returns false.", 36 | validConfig: true, 37 | request: { 38 | algorithm: "TEST", 39 | method: 'foo', 40 | visitor: { 41 | foo: function(request) { 42 | return false; 43 | } 44 | }, 45 | request: "whatever" 46 | }, 47 | expectedResults: { 48 | error: null, 49 | result: false, 50 | callbackCalled: true 51 | } 52 | }); 53 | })(); 54 | 55 | (function() { 56 | testAlgorithmVisitorCallback({ 57 | testName: "Simple callback on undefined visitor method, no error.", 58 | validConfig: true, 59 | request: { 60 | algorithm: "TEST", 61 | method: 'foo', 62 | visitor: { 63 | }, 64 | request: "whatever" 65 | }, 66 | expectedResults: { 67 | error: null, 68 | result: true, 69 | callbackCalled: false 70 | } 71 | }); 72 | })(); 73 | 74 | (function() { 75 | testAlgorithmVisitorCallback({ 76 | testName: "Attempt to callback a visitor method that is not a function.", 77 | validConfig: false, 78 | facade: { 79 | watcher: 5 80 | }, 81 | request: { 82 | algorithm: "TEST", 83 | method: 'foo', 84 | request: "whatever" 85 | }, 86 | expectedResults: { 87 | error: 'TEST visitor interface method \'watcher\' is type \'[object Number]\' instead of \'[object Function]\' as expected.', 88 | result: null, 89 | callbackCalled: false 90 | } 91 | }); 92 | })(); 93 | 94 | 95 | (function() { 96 | testAlgorithmVisitorCallback({ 97 | testName: "Attempt to callback a visitor method that does not return a Boolean.", 98 | validConfig: false, 99 | request: { 100 | algorithm: "TEST", 101 | method: 'foo', 102 | request: "whatever", 103 | visitor: { 104 | foo: function(request) { 105 | return 5; 106 | } 107 | } 108 | }, 109 | expectedResults: { 110 | error: 'TEST visitor interface error in callback function \'watcher\'. Function returned type \'[object Number]\' instead of expected \'[object Boolean]\'.', 111 | result: null, 112 | callbackCalled: true 113 | } 114 | }); 115 | })(); 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /test/fixture/dfv-results-recorder.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = SearchPathRecorder = (function() { 4 | 5 | function SearchPathRecorder(chainedVisitor_) { 6 | var self = this; 7 | this.results = []; 8 | this.step = 0; 9 | this.time = 1; 10 | this.chainedVisitor = (chainedVisitor_ !== null) && chainedVisitor_ || {}; 11 | 12 | this.visitorInterface = { 13 | // request: { u: vertexId, g: DirectedGraph } 14 | initializeVertex: function(request) { 15 | self.results.push(self.step++ + " initializeVertex " + request.u); 16 | if (self.chainedVisitor.initializeVertex) { 17 | return self.chainedVisitor.initializeVertex(request); 18 | } 19 | return true; 20 | }, 21 | // request: { u: vertexId, g: DirectedGraph } 22 | startVertex: function (request) { 23 | self.results.push(self.step++ + " startVertex " + request.u); 24 | if (self.chainedVisitor.startVertex) { 25 | return self.chainedVisitor.startVertex(request); 26 | } 27 | return true; 28 | }, 29 | // request: { u: vertexId, g: DirectedGraph } 30 | discoverVertex: function(request) { 31 | self.results.push(self.step++ + " discoverVertex " + request.u + " at time " + self.time); 32 | self.time++; 33 | if (self.chainedVisitor.discoverVertex) { 34 | return self.chainedVisitor.discoverVertex(request); 35 | } 36 | return true; 37 | }, 38 | // request: { e: { u: VertexId, v: VertexId }, g: DirectedGraph } 39 | examineEdge: function(request) { 40 | self.results.push(self.step++ + " examineEdge [" + request.e.u + "," + request.e.v + "]"); 41 | if (self.chainedVisitor.examineEdge) { 42 | return self.chainedVisitor.examineEdge(request); 43 | } 44 | return true; 45 | }, 46 | // request: { e: { u: VertexId, v: VertexId }, g: DirectedGraph } 47 | treeEdge: function(request) { 48 | self.results.push(self.step++ + " treeEdge [" + request.e.u + "," + request.e.v + "]"); 49 | if (self.chainedVisitor.treeEdge) { 50 | return self.chainedVisitor.treeEdge(request); 51 | } 52 | return true; 53 | }, 54 | // request: { e: { u: VertexId, v: VertexId }, g: DirectedGraph } 55 | backEdge: function(request) { 56 | self.results.push(self.step++ + " backEdge [" + request.e.u + "," + request.e.v + "]"); 57 | if (self.chainedVisitor.backEdge) { 58 | return self.chainedVisitor.backEdge(request); 59 | } 60 | return true; 61 | }, 62 | // request: { e: { u: VertexId, v: VertexId }, g: DirectedGraph } 63 | forwardOrCrossEdge: function(request) { 64 | self.results.push(self.step++ + " forwardOrCrossEdge [" + request.e.u + "," + request.e.v + "]"); 65 | if (self.chainedVisitor.forwardOrCrossEdge) { 66 | return self.chainedVisitor.forwardOrCrossEdge(request); 67 | } 68 | return true; 69 | }, 70 | // request: { u: vertexId, g: DirectedGraph } 71 | finishVertex: function(request) { 72 | self.results.push(self.step++ + " finishVertex " + request.u + " at time " + self.time); 73 | self.time++; 74 | if (self.chainedVisitor.finishVertex) { 75 | return self.chainedVisitor.finishVertex(request); 76 | } 77 | return true; 78 | }, 79 | // request: { e: { u: VertexId, v: VertexId }, g: DirectedGraph } 80 | finishEdge: function(request) { 81 | self.results.push(self.step++ + " finishEdge [" + request.e.u + "," + request.e.v + "]"); 82 | self.time++; 83 | if (self.chainedVisitor.finishVertex) { 84 | return self.chainedVisitor.finishVertex(request); 85 | } 86 | return true; 87 | } 88 | }; 89 | 90 | this.toJSON = function() { 91 | return JSON.stringify(self.results); 92 | }; 93 | 94 | } 95 | 96 | return SearchPathRecorder; 97 | 98 | })(); 99 | 100 | -------------------------------------------------------------------------------- /test/fixture/bfv-results-recorder.js: -------------------------------------------------------------------------------- 1 | // bfv-results-recorder.js 2 | 3 | module.exports = SearchPathRecorder = (function() { 4 | 5 | function SearchPathRecorder(chainedVisitor_) { 6 | var self = this; 7 | this.results = []; 8 | this.step = 0; 9 | this.chainedVisitor = (chainedVisitor_ !== null) && chainedVisitor_ || {}; 10 | 11 | this.visitorInterface = { 12 | // request = { u: vertexId, g: DirectedGraph } 13 | initializeVertex: function(request) { 14 | self.results.push(self.step++ + " initializeVertex " + request.u); 15 | if (self.chainedVisitor.initializeVertex) { 16 | return self.chainedVisitor.initializeVertex(request); 17 | } 18 | return true; 19 | }, 20 | // request = { u: vertexId, g: DirectedGraph } 21 | startVertex: function(request) { 22 | self.results.push(self.step++ + " startVertex " + request.u); 23 | if (self.chainedVisitor.startVertex) { 24 | return self.chainedVisitor.startVertex(request); 25 | } 26 | return true; 27 | }, 28 | // request = { u: vertexId, g: DirectedGraph } 29 | discoverVertex: function(request) { 30 | self.results.push(self.step++ + " discoverVertex " + request.u); 31 | if (self.chainedVisitor.discoverVertex) { 32 | return self.chainedVisitor.discoverVertex(request); 33 | } 34 | return true; 35 | }, 36 | // request = { u: vertexId, g: DirectedGraph } 37 | examineVertex: function(request) { 38 | self.results.push(self.step++ + " examineVertex " + request.u); 39 | if (self.chainedVisitor.examineVertex) { 40 | return self.chainedVisitor.examineVertex(request); 41 | } 42 | return true; 43 | }, 44 | // request = { e: { u: vertexId, v: vertexId }, g: DirectedGraph } 45 | examineEdge: function(request) { 46 | self.results.push(self.step++ + " examineEdge [" + request.e.u + "," + request.e.v + "]"); 47 | if (self.chainedVisitor.examineEdge) { 48 | return self.chainedVisitor.examineEdge(request); 49 | } 50 | return true; 51 | }, 52 | // request = { e: { u: vertexId, v: vertexId }, g: DirectedGraph } 53 | treeEdge: function(request) { 54 | self.results.push(self.step++ + " treeEdge [" + request.e.u + "," + request.e.v + "]"); 55 | if (self.chainedVisitor.treeEdge) { 56 | return self.chainedVisitor.treeEdge(request); 57 | } 58 | return true; 59 | }, 60 | // request = { e: { u: vertexId, v: vertexId }, g: DirectedGraph } 61 | nonTreeEdge: function(request) { 62 | self.results.push(self.step++ + " nonTreeEdge [" + request.e.u + "," + request.e.v + "]"); 63 | if (self.chainedVisitor.nonTreeEdge) { 64 | return self.chainedVisitor.nonTreeEdge(request); 65 | } 66 | return true; 67 | }, 68 | // request = { e: { u: vertexId, v: vertexId }, g: DirectedGraph } 69 | grayTarget: function(request) { 70 | self.results.push(self.step++ + " grayTarget [" + request.e.u + "," + request.e.v + "]"); 71 | if (self.chainedVisitor.grayTarget) { 72 | return self.chainedVisitor.grayTarget(request); 73 | } 74 | return true; 75 | }, 76 | // request = { e: { u: vertexId, v: vertexId }, g: DirectedGraph } 77 | blackTarget: function(request) { 78 | self.results.push(self.step++ + " blackTarget [" + request.e.u + "," + request.e.v + "]"); 79 | if (self.chainedVisitor.blackTarget) { 80 | return self.chainedVisitor.blackTarget(request); 81 | } 82 | return true; 83 | }, 84 | // request = { u: vertexId, g: DirectedGraph } 85 | finishVertex: function(request) { 86 | self.results.push(self.step++ + " finishVertex " + request.u); 87 | if (self.chainedVisitor.finishVertex) { 88 | return self.chainedVisitor.finishVertex(request); 89 | } 90 | return true; 91 | } 92 | }; 93 | 94 | this.toJSON = function() { 95 | return JSON.stringify(self.results); 96 | }; 97 | } 98 | 99 | return SearchPathRecorder; 100 | 101 | })(); 102 | -------------------------------------------------------------------------------- /src/arc_core_digraph_in_params.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | var helperFunctions = require('./arc_core_graph_util'); 12 | 13 | var verifyVertexReadRequest = function(request_) { 14 | var response = { error: null, result: false }; 15 | var jstype = helperFunctions.JSType(request_); 16 | if (jstype !== '[object String]') { 17 | response.error = "Invalid value type '" + jstype + "' found when expecting vertex read request. Expected '[object String]'."; 18 | } else { 19 | response.result = true; 20 | } 21 | return response; 22 | }; 23 | 24 | var verifyVertexWriteRequest = function(request_) { 25 | var response = { error: null, result: false }; 26 | var inBreakScope = false; 27 | while (!inBreakScope) { 28 | inBreakScope = true; 29 | var jstype = helperFunctions.JSType(request_); 30 | if (jstype !== '[object Object]') { 31 | response.error = "Invalid value type '" + jstype + "' found when expecting a vertex write request object."; 32 | break; 33 | } 34 | jstype = helperFunctions.JSType(request_.u); 35 | if (jstype !== '[object String]') { 36 | response.error = "Invalid value type '" + jstype + "' found looking for vertex ID string property 'u' in vertex write request object."; 37 | break; 38 | } 39 | jstype = helperFunctions.JSType(request_.p); 40 | if (jstype === '[object Function]') { 41 | response.error = "Invalid value type '" + jstype + " found while inspecting vertex property 'p' in vertex write request object. Must be serializable to JSON!"; 42 | break; 43 | } 44 | response.result = true; 45 | } 46 | return response; 47 | }; 48 | 49 | var verifyEdgeReadRequest = function(request_) { 50 | var response = { error: null, result: false }; 51 | var inBreakScope = false; 52 | while (!inBreakScope) { 53 | inBreakScope = true; 54 | var jstype = helperFunctions.JSType(request_); 55 | if (jstype !== '[object Object]') { 56 | response.error = "Invalid value type '" + jstype + "' found when expecting edge read request object."; 57 | break; 58 | } 59 | jstype = helperFunctions.JSType(request_.u); 60 | if (jstype !== '[object String]') { 61 | response.error = "Invalid value type '" + jstype + "' found looking for vertex ID string property 'u' in edge read request object."; 62 | break; 63 | } 64 | jstype = helperFunctions.JSType(request_.v); 65 | if (jstype !== '[object String]') { 66 | response.error = "Invalid value type '" + jstype + "' found looking for vertex ID string property 'v' in edge read request object."; 67 | break; 68 | } 69 | response.result = true; 70 | } 71 | return response; 72 | }; 73 | 74 | var verifyEdgeWriteRequest = function(request_) { 75 | var response = { error: null, result: false }; 76 | var inBreakScope = false; 77 | while (!inBreakScope) { 78 | inBreakScope = true; 79 | var jstype = helperFunctions.JSType(request_); 80 | if (jstype !== '[object Object]') { 81 | response.error = "Invalid value type '" + jstype + "' found when expecting edge write request object."; 82 | break; 83 | } 84 | jstype = helperFunctions.JSType(request_.e); 85 | if (jstype !== '[object Object]') { 86 | response.error = "Invalid value type '" + jstype + "' found looking for edge descriptor object 'e' in edge write request object."; 87 | break; 88 | } 89 | jstype = helperFunctions.JSType(request_.e.u); 90 | if (jstype !== '[object String]') { 91 | response.error = "Invalid value type '" + jstype + "' found looking for vertex ID string property 'e.u' in edge write request object."; 92 | break; 93 | } 94 | jstype = helperFunctions.JSType(request_.e.v); 95 | if (jstype !== '[object String]') { 96 | response.error = "Invalid value type '" + jstype + "' found looking for vertex ID string property 'e.v' in edge write request object."; 97 | break; 98 | } 99 | jstype = helperFunctions.JSType(request_.p); 100 | if (jstype === '[object Function]') { 101 | response.error = "Invalid value type '" + jstype + "' found while insecting edge property 'p' in edge write request object. Must be serializable to JSON!"; 102 | break; 103 | } 104 | response.result = true; 105 | } 106 | return response; 107 | }; 108 | 109 | module.exports = { 110 | verifyVertexReadRequest: verifyVertexReadRequest, 111 | verifyVertexWriteRequest: verifyVertexWriteRequest, 112 | verifyEdgeReadRequest: verifyEdgeReadRequest, 113 | verifyEdgeWriteRequest: verifyEdgeWriteRequest 114 | }; 115 | -------------------------------------------------------------------------------- /test/test-digraph-algorithm-transpose.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('chai').assert; 3 | var expect = require('chai').expect; 4 | var should = require('chai').should; 5 | var uuid = require('node-uuid'); 6 | 7 | var testModule = require('./module-under-test'); 8 | 9 | var DirectedGraph = testModule('arc_core_digraph').DirectedGraph; 10 | var transposeDigraph = testModule('arc_core_digraph_algorithm_transpose'); 11 | 12 | describe("Directed graph tranposition test", function() { 13 | 14 | var digraphInput = null; 15 | var digraphOutput = null; 16 | 17 | before(function() { 18 | digraphInput = new DirectedGraph(); 19 | digraphInput.addVertex({ u: "process", p: { testProperties: { description: "this vertex represents a program proces" }}}); 20 | digraphInput.addVertex({ u: "inputA", p: { testProperties: { description: "this vertex represents data input A" }}}); 21 | digraphInput.addVertex({ u: "inputB", p: { testProperties: { description: "this vertex representa data input B" }}}); 22 | digraphInput.addVertex({ u: "outputA", p: { testProperties: { description: "this vertex represents data output A" }}}); 23 | digraphInput.addVertex({ u: "outputB", p: { testProperties: { description: "this vertex represents data output B" }}}); 24 | digraphInput.addVertex({ u: "outputC", p: { testProperties: { description: "this vertex represents data output C" }}}); 25 | digraphInput.addEdge({ e: { u: "inputA", v: "process" }, p: { testProperties: { description: "this edge represents the flow of data from input A to process" }}}); 26 | digraphInput.addEdge({ e: { u: "inputB", v: "process" }, p: { testProperties: { description: "this edge represents the flow of data from input B to process" }}}); 27 | digraphInput.addEdge({ e: { u: "process", v: "outputA" }, p: { testProperties: { description: "this edge represents the flow of data from process to output A" }}}); 28 | digraphInput.addEdge({ e: { u: "process", v: "outputB" }, p: { testProperties: { description: "this edge represents the flow of data from process to output B" }}}); 29 | digraphInput.addEdge({ e: { u: "process", v: "outputC" }, p: { testProperties: { description: "this edge represents the flow of data from process to output C" }}}); 30 | // The transposed digraph is a copy of the original with the edge directions reversed. 31 | var innerResponse = transposeDigraph(digraphInput); 32 | assert.isNull(innerResponse.error); 33 | digraphOutput = innerResponse.result; 34 | }); 35 | 36 | describe("macro-level tests", function() { 37 | it("input digraph should contain six vertices", function() { 38 | assert.equal(digraphInput.verticesCount(), 6); 39 | }); 40 | 41 | it("input digraph should contain five edges", function() { 42 | assert.equal(digraphInput.edgesCount(), 5); 43 | }); 44 | 45 | it("input digraph should have two root vertices", function() { 46 | assert.equal(digraphInput.rootVerticesCount(), 2); 47 | }); 48 | 49 | it("input digraph should have three leaf vertices", function() { 50 | assert.equal(digraphInput.leafVerticesCount(), 3); 51 | }); 52 | 53 | it("output digraph should contain six vertices", function() { 54 | assert.equal(digraphOutput.verticesCount(), 6); 55 | }); 56 | 57 | it("output digraph should contain five edges", function() { 58 | assert.equal(digraphOutput.edgesCount(), 5); 59 | }); 60 | 61 | it("output digraph should have three root vertices", function() { 62 | assert.equal(digraphOutput.rootVerticesCount(), 3); 63 | }); 64 | 65 | it("output digraph should have two leaf vertices", function() { 66 | assert.equal(digraphOutput.leafVerticesCount(), 2); 67 | }); 68 | }); 69 | 70 | describe("check out-degree of roots and leaves before/after transposition", function() { 71 | it("inputA should have out-degree one in the input graph", function() { 72 | assert.equal(digraphInput.outDegree("inputA"), 1); 73 | }); 74 | 75 | it("inputA should have out-degree zero in the output graph", function() { 76 | assert.equal(digraphOutput.outDegree("inputA"), 0); 77 | }); 78 | 79 | it("outputC should have out-degree zero in the input graph", function() { 80 | assert.equal(digraphInput.outDegree("outputC"), 0); 81 | }); 82 | 83 | it("outputC should have out-degree one in the output graph", function() { 84 | assert.equal(digraphOutput.outDegree("outputC"), 1); 85 | }); 86 | }); 87 | 88 | describe("vertex 'process' vertex before/after transposition", function() { 89 | it("process should have in-degree two in the input graph", function() { 90 | assert.equal(digraphInput.inDegree("process"), 2); 91 | }); 92 | 93 | it("process should have in-degree three in the output graph", function() { 94 | assert.equal(digraphOutput.inDegree("process"), 3); 95 | }); 96 | 97 | it("process should have out-degree three in the input graph", function() { 98 | assert.equal(digraphInput.outDegree("process"), 3); 99 | }); 100 | 101 | it("process should have out-degree two in the output graph", function() { 102 | assert.equal(digraphOutput.outDegree("process"), 2); 103 | }); 104 | }); 105 | 106 | 107 | }); 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Encapsule Project](https://encapsule.io/images/blue-burst-encapsule.io-icon-72x72.png "Encapsule Project") Encapsule Project: [GitHub](https://gihub.com/Encapsule) / [Twitter](https://twitter.com/Encapsule) 2 | **Exploration of declarative programming with data models and graph theory using JavaScript, Node.js, and HTML5.** 3 | 4 | # Encapsule/jsgraph v0.7.1 5 | 6 | [![Build Status](https://travis-ci.org/Encapsule/jsgraph.svg?branch=master)](https://travis-ci.org/Encapsule/jsgraph) 7 | 8 | **"Begin at the beginning," the King said very gravely. "and go on till you come to the end: then stop." - Lewis Carroll, Alice in Wonderland** 9 | 10 | See also: [Mathematical Graph Theory](https://en.wikipedia.org/wiki/Graph_theory) 11 | 12 | **NEW DOCS** for the v0.7.1 release: **[ARCcore.graph](https://encapsule.io/docs/ARCcore/graph)** 13 | 14 | ## About 15 | 16 | **This is a data modeling and algorithms library. It does not draw graphs in your browser!** 17 | 18 | [Encapsule/jsgraph](https://github.com/Encapsule/jsgraph) (aka ARCcore.graph) is a JavaScript library for storing and processing in-memory directed graph data sets inspired by [Jeremy Siek's](https://twitter.com/jeremysiek) work on the [Boost C++ Graph Library](http://www.boost.org/doc/libs/1_63_0/libs/graph/doc/table_of_contents.html) (BGL). The library is not a complete port of the BGL but does provide a very useful subset its functionality that is useful for building data-driven JavaScript applications. 19 | 20 | Briefly, jsgraph library provides: 21 | 22 | - [DirectedGraph](https://encapsule.io/docs/ARCcore/graph/digraph) container class: 23 | - [Vertex](https://encapsule.io/docs/ARCcore/graph/digraph/vertices) and [edge](https://encapsule.io/docs/ARCcore/graph/digraph/edges) add, remove, enumerate, and existence testing. And, vertex and edge-associated property maps. 24 | - JSON [serialization and deserialization](https://encapsule.io/docs/ARCcore/graph/digraph/serialize) of the container. 25 | - [Algorithms](https://encapsule.io/docs/ARCcore/graph/algorithms): 26 | - Directed graph [transpose](https://encapsule.io/docs/ARCcore/graph/algorithms/digraph-transpose) algorithm. 27 | - Non-recursive visitor pattern implementation of [breadth-first visit and search](https://encapsule.io/docs/ARCcore/graph/algorithms/digraph-bft) algorithms with edge classification. 28 | - Non-recursive visitor pattern implementation of [depth-first visit and search](https://encapsule.io/docs/ARCcore/graph/algorithms/digraph-dft) algorithm with edge classification. 29 | 30 | 31 | ### Packaging 32 | 33 | Encapsule/jsgraph is a stand-alone JavaScript library that may be used directly in Node.js applications. Or in the browser via [webpack](https://webpack.github.io/). 34 | 35 | The library is also distributed as part of the [Encapsule/ARCcore](https://encapsule.io/docs/ARCcore) package that contains a number of other libraries for modeling and processing complex in-memory data in JavaScript applications that some of you may find interesting and useful. 36 | 37 | ### Contributing 38 | 39 | This library is used in production applications. And, in ridiculous [derived science projects](https://encapsule.io). So, the bar is pretty high for taking changes (particularly breaking changes). And, PR's need to come with [tests](https://travis-ci.org/Encapsule/jsgraph)! Exceptions made on a case-by-case basis for nice people and important projects with wide benefit. 40 | 41 | 42 | ## Release Notes 43 | 44 | **v0.7.1 is a maintenance release** 45 | - [Encapsule/jsgraph](https://github.com/Encapsule/jsgraph) sources are now officially part of the [Encapsule/ARCcore](https://github.com/Encapsule/ARCcore) package. 46 | - Travis CI updated for Node.js v6.10.x LTS and v7.9.0 current releases. Older builds dropped. 47 | - Fixed a single test break caused by latest Node.js increasing verbosity of JSON parse error to include character position of failure. 48 | - Documentation has been revised and is now available on the Encapsule Project website: **[ARCcore.graph](https://encapsule.io/docs/ARCcore/graph)**. 49 | 50 | 51 | **v0.7 is a breaking API change and documentation release** 52 | 53 | - Added new method `DirectedGraph.stringify` 54 | - Changed method semantics of `DirectedGraph.toJSON` to return a serializable object instead of a JSON-encoded string. 55 | - Alias method `DirectedGraph.toObject` to call `DirectedGraph.toJSON`. The `toObject` method is now deprecated and will be removed in a future release. 56 | - Updated documentation: 57 | - Per above breaking changes to the `DirectedGraph` serialization API. 58 | - Added additional information on set/get of `DirectedGraph` name and description properties. 59 | 60 | **v0.6 is a bug fix release that's API-compatible with v0.5** 61 | 62 | - DFT algorithm bug fixes impacting order and identity of client visitor callbacks. 63 | - Better error handling on bad developer-supplied visitor interfaces. 64 | - Better error handling for BFT/DFT algorithm empty start vector case. 65 | - You can now set `name` and `description` string properties on a `DirectedGraph`: 66 | 67 | **v0.5 is a breaking upgrade for users of v0.4** 68 | 69 | - Stylistic changes are required to v0.4 clients to upgrade. 70 | - No more exceptions. jsgraph now returns error/result response objects. 71 | - Breadth-first * algorithms coalesced into `breadthFirstTraverse`. 72 | - Depth-first * algorithms coalesced into `depthFirstTraverse`. 73 | - Algorithms now support early terminate under client control. 74 | - ~400 new tests added for v0.5 release. 75 | - Documentation and example updates. 76 | 77 | 78 | --- 79 | 80 | Copyright © 2014-2017 [Christopher D. Russell](https://github.com/ChrisRus) 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /test/test-digraph-container-create.js: -------------------------------------------------------------------------------- 1 | // test-create-digraph.js 2 | 3 | var testCreateDirectedGraph = require('./fixture/test-runner-create-digraph'); 4 | 5 | testCreateDirectedGraph({ 6 | testName: "Bogus construction #1", validConfig: false, 7 | request: "Bull shit", 8 | expectedResults: { 9 | error: 'DirectedGraph constructor failed: Exception occurred while parsing JSON: Unexpected token B in JSON at position 0', 10 | result: '' 11 | } 12 | }); 13 | 14 | 15 | testCreateDirectedGraph({ 16 | testName: "Bogus construction #2", validConfig: false, 17 | request: function () {}, 18 | expectedResults: { 19 | error: 'DirectedGraph constructor failed: Invalid reference to \'[object Function]\' passed instead of expected JSON (or equivalent object) reference.', 20 | result: '' 21 | } 22 | }); 23 | 24 | testCreateDirectedGraph({ 25 | testName: "Bogus construction #3", validConfig: false, 26 | request: [], 27 | expectedResults: { 28 | error: 'DirectedGraph constructor failed: Invalid reference to \'[object Array]\' passed instead of expected JSON (or equivalent object) reference.', 29 | result: '' 30 | } 31 | }); 32 | 33 | testCreateDirectedGraph({ 34 | testName: "Bogus construction #4", validConfig: false, 35 | request: "[]", 36 | expectedResults: { 37 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected top-level object but found \'[object Array]\'.', 38 | result: '' 39 | } 40 | }); 41 | 42 | testCreateDirectedGraph({ 43 | testName: "Bogus construction #5", validConfig: false, 44 | request: { vlist: "What's up, man?" }, 45 | expectedResults: { 46 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected \'vlist\' (vertices) to be an array but found \'[object String]\'.', 47 | result: '' 48 | } 49 | }); 50 | 51 | testCreateDirectedGraph({ 52 | testName: "Bogus construction #6", validConfig: false, 53 | request: { vlist: [], elist: "What's up, man?" }, 54 | expectedResults: { 55 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected \'elist\' (edges) to be an array but found \'[object String]\'.', 56 | result: '' 57 | } 58 | }); 59 | 60 | testCreateDirectedGraph({ 61 | testName: "Bogus construction #7", validConfig: false, 62 | request: { vlist: [ "no way" ] }, 63 | expectedResults: { 64 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected vertex descriptor object in \'vlist\' array but found \'[object String]\' instead.', 65 | result: '' 66 | } 67 | }); 68 | 69 | testCreateDirectedGraph({ 70 | testName: "Bogus construction #8", validConfig: false, 71 | request: { vlist: [ {} ] }, 72 | expectedResults: { 73 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected vertex descriptor property \'u\' to be a string but found \'[object Undefined]\' instead.', 74 | result: '' 75 | } 76 | }); 77 | 78 | testCreateDirectedGraph({ 79 | testName: "Bogus construction #9", validConfig: false, 80 | request: { elist: [ "no way" ] }, 81 | expectedResults: { 82 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected edge descriptor object in \'elist\' array but found \'[object String]\' instead.', 83 | result: '' 84 | } 85 | }); 86 | 87 | testCreateDirectedGraph({ 88 | testName: "Bogus construction #10", validConfig: false, 89 | request: { elist: [ { x: 'test' } ] }, 90 | expectedResults: { 91 | error: 'DirectedGraph constructor failed: JSON semantics error: Edge record in \'elist\' should define edge descriptor object \'e\' but but found \'[object Undefined]\' instead.', 92 | result: '' 93 | } 94 | }); 95 | 96 | testCreateDirectedGraph({ 97 | testName: "Bogus construction #11", validConfig: false, 98 | request: { elist: [ { e: "no way" } ] }, 99 | expectedResults: { 100 | error: 'DirectedGraph constructor failed: JSON semantics error: Edge record in \'elist\' should define edge descriptor object \'e\' but but found \'[object String]\' instead.', 101 | result: '' 102 | } 103 | }); 104 | 105 | testCreateDirectedGraph({ 106 | testName: "Bogus construction #12", validConfig: false, 107 | request: { elist: [ { e: {} } ] }, 108 | expectedResults: { 109 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected edge descriptor property \'e.u\' to be a string but found \'[object Undefined]\' instead.', 110 | result: '' 111 | } 112 | }); 113 | 114 | testCreateDirectedGraph({ 115 | testName: "Bogus construction #13", validConfig: false, 116 | request: { elist: [ { e: { u: 'foo' } } ] }, 117 | expectedResults: { 118 | error: 'DirectedGraph constructor failed: JSON semantics error: Expected edge descriptor property \'e.v\' to be a string but found \'[object Undefined]\' instead.', 119 | result: '' 120 | } 121 | }); 122 | 123 | testCreateDirectedGraph({ 124 | testName: "Default construction", validConfig: true, 125 | expectedResults: { 126 | error: '', 127 | result: '{"name":"","description":"","vlist":[],"elist":[]}' 128 | } 129 | }); 130 | 131 | testCreateDirectedGraph({ 132 | testName: "More advanced example", validConfig: true, 133 | request: { 134 | vlist: [ 135 | { u: 'all', p: "whatever vertex property" }, 136 | { u: 'work', p: { x: 6 }}, 137 | { u: 'and' }, 138 | { u: 'no' }, 139 | { u: 'play' } 140 | ], 141 | elist: [ 142 | { e: { u: 'all', v: 'work' }, p: "whatever edge property" }, 143 | { e: { u: 'work', v: 'and' } }, 144 | { e: { u: 'and', v: 'no' } }, 145 | { e: { u: 'no', v: 'play' }, p: "leads to superior code :)" } 146 | ] 147 | }, 148 | expectedResults: { 149 | error: '', 150 | result: '{"name":"","description":"","vlist":[{"u":"all","p":"whatever vertex property"},{"u":"work","p":{"x":6}}],"elist":[{"e":{"u":"all","v":"work"},"p":"whatever edge property"},{"e":{"u":"work","v":"and"}},{"e":{"u":"and","v":"no"}},{"e":{"u":"no","v":"play"},"p":"leads to superior code :)"}]}' 151 | } 152 | }); 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/arc_core_digraph_import.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | module.exports = function (digraph_, jsonOrObject_) { 12 | 13 | var jsonParse; 14 | var getType = function(ref_) { return Object.prototype.toString.call(ref_); }; 15 | var response = { error: null, result: null }; 16 | var errors = []; 17 | var inBreakScope = false; 18 | 19 | var processVertex = function(vertexDescriptor_) { 20 | type = getType(vertexDescriptor_); 21 | if (type !== '[object Object]') { 22 | errors.unshift("JSON semantics error: Expected vertex descriptor object in 'vlist' array but found '" + type + "' instead."); 23 | } else { 24 | type = getType(vertexDescriptor_.u); 25 | if (type !== '[object String]') { 26 | errors.unshift("JSON semantics error: Expected vertex descriptor property 'u' to be a string but found '" + type + "' instead."); 27 | } else { 28 | digraph_.addVertex({ u: vertexDescriptor_.u, p: vertexDescriptor_.p}); 29 | } 30 | } 31 | }; 32 | 33 | var processEdge = function (edgeDescriptor_) { 34 | type = getType(edgeDescriptor_); 35 | if (type !== '[object Object]') { 36 | errors.unshift("JSON semantics error: Expected edge descriptor object in 'elist' array but found '" + type + "' instead."); 37 | } else { 38 | type = getType(edgeDescriptor_.e); 39 | if (type !== '[object Object]') { 40 | errors.unshift("JSON semantics error: Edge record in 'elist' should define edge descriptor object 'e' but but found '" + type + "' instead."); 41 | } else { 42 | type = getType(edgeDescriptor_.e.u); 43 | if (type !== '[object String]') { 44 | errors.unshift("JSON semantics error: Expected edge descriptor property 'e.u' to be a string but found '" + type + "' instead."); 45 | } else { 46 | type = getType(edgeDescriptor_.e.v); 47 | if (type !== '[object String]') { 48 | errors.unshift("JSON semantics error: Expected edge descriptor property 'e.v' to be a string but found '" + type + "' instead."); 49 | } else { 50 | digraph_.addEdge({ e: edgeDescriptor_.e, p: edgeDescriptor_.p}); 51 | } 52 | } 53 | } 54 | } 55 | }; 56 | 57 | while (!inBreakScope) { 58 | inBreakScope = true; 59 | 60 | var type = getType(jsonOrObject_); 61 | switch (type) { 62 | case '[object String]': 63 | try { 64 | jsonParse = JSON.parse(jsonOrObject_); 65 | } catch (exception_) { 66 | errors.unshift("Exception occurred while parsing JSON: " + exception_.message); 67 | } 68 | break; 69 | case '[object Object]': 70 | jsonParse = jsonOrObject_; 71 | break; 72 | default: 73 | errors.unshift("Invalid reference to '" + type + "' passed instead of expected JSON (or equivalent object) reference."); 74 | } 75 | if (errors.length) { 76 | break; 77 | } 78 | 79 | type = getType(jsonParse); 80 | if (type !== '[object Object]') { 81 | errors.unshift("JSON semantics error: Expected top-level object but found '" + type + "'."); 82 | break; 83 | } 84 | 85 | type = getType(jsonParse.name); 86 | switch (type) { 87 | case '[object Undefined]': 88 | jsonParse.name = ""; 89 | break; 90 | case '[object String]': 91 | break; 92 | default: 93 | errors.unshift("JSON semantics error: Expected 'name' to be a string but found '" + type + "'."); 94 | break; 95 | } 96 | digraph_.setGraphName(jsonParse.name); 97 | 98 | type = getType(jsonParse.description); 99 | switch (type) { 100 | case '[object Undefined]': 101 | jsonParse.description = ""; 102 | break; 103 | case '[object String]': 104 | break; 105 | default: 106 | error.unshift("JSON semantics error: Expected 'description' to be a string but found '" + type + "'."); 107 | break; 108 | } 109 | digraph_.setGraphDescription(jsonParse.description); 110 | 111 | type = getType(jsonParse.vlist); 112 | switch (type) { 113 | case '[object Undefined]': 114 | jsonParse.vlist = []; // default to empty vertex list 115 | break; 116 | case '[object Array]': 117 | // do nothing the array is parsed below 118 | break; 119 | default: 120 | errors.unshift("JSON semantics error: Expected 'vlist' (vertices) to be an array but found '" + type + "'."); 121 | break; 122 | } 123 | if (errors.length) { 124 | break; 125 | } 126 | 127 | type = getType(jsonParse.elist); 128 | switch (type) { 129 | case '[object Undefined]': 130 | jsonParse.elist = []; // default to empty edge list 131 | break; 132 | case '[object Array]': 133 | // do nothing 134 | break; 135 | default: 136 | errors.unshift("JSON semantics error: Expected 'elist' (edges) to be an array but found '" + type + "'."); 137 | } 138 | if (errors.length) { 139 | break; 140 | } 141 | 142 | jsonParse.vlist.forEach(processVertex); 143 | if (errors.length) { 144 | break; 145 | } 146 | 147 | jsonParse.elist.forEach(processEdge); 148 | if (errors.length) { 149 | break; 150 | } 151 | 152 | } // while !inBreakScope 153 | 154 | if (errors.length) { 155 | response.error = errors.join(' '); 156 | } else { 157 | response.result = true; 158 | } 159 | 160 | return response; 161 | 162 | }; 163 | -------------------------------------------------------------------------------- /test/test-digraph-in-param-validators.js: -------------------------------------------------------------------------------- 1 | // test-digraph-param-vertex-read.js 2 | 3 | var testDigraphParams = require('./fixture/test-runner-digraph-in-params'); 4 | 5 | testDigraphParams({ 6 | testName: "Undefined request", 7 | validatorFunction: 'verifyVertexReadRequest', 8 | validConfig: false, 9 | expectedResults: { 10 | error: 'Invalid value type \'[object Undefined]\' found when expecting vertex read request. Expected \'[object String]\'.', 11 | } 12 | }); 13 | 14 | testDigraphParams({ 15 | testName: "Bad request type", 16 | validatorFunction: 'verifyVertexReadRequest', 17 | validConfig: false, 18 | request: [ "Hey, man!" ], 19 | expectedResults: { 20 | error: 'Invalid value type \'[object Array]\' found when expecting vertex read request. Expected \'[object String]\'.', 21 | } 22 | }); 23 | 24 | testDigraphParams({ 25 | testName: "Valid request", 26 | validatorFunction: 'verifyVertexReadRequest', 27 | validConfig: true, 28 | request: "vertexID", 29 | expectedResults: { 30 | result: true 31 | } 32 | }); 33 | 34 | testDigraphParams({ 35 | testName: "Missing request", 36 | validatorFunction: 'verifyVertexWriteRequest', 37 | validConfig: false, 38 | expectedResults: { 39 | error: 'Invalid value type \'[object Undefined]\' found when expecting a vertex write request object.' 40 | } 41 | }); 42 | 43 | testDigraphParams({ 44 | testName: "Empty request", 45 | validatorFunction: 'verifyVertexWriteRequest', 46 | validConfig: false, 47 | request: {}, 48 | expectedResults: { 49 | error: 'Invalid value type \'[object Undefined]\' found looking for vertex ID string property \'u\' in vertex write request object.' 50 | } 51 | }); 52 | 53 | testDigraphParams({ 54 | testName: "Bad 'u' type", 55 | validatorFunction: 'verifyVertexWriteRequest', 56 | validConfig: false, 57 | request: { u: [ "Hey, man!"] }, 58 | expectedResults: { 59 | error: 'Invalid value type \'[object Array]\' found looking for vertex ID string property \'u\' in vertex write request object.' 60 | } 61 | }); 62 | 63 | testDigraphParams({ 64 | testName: "Valid 'u'", 65 | validatorFunction: 'verifyVertexWriteRequest', 66 | validConfig: true, 67 | request: { u: "Hey, man!" }, 68 | expectedResults: { 69 | result: true 70 | } 71 | }); 72 | 73 | testDigraphParams({ 74 | testName: "Invalid 'p' type", 75 | validatorFunction: 'verifyVertexWriteRequest', 76 | validConfig: false, 77 | request: { u: "Hey, man!", p: function() {} }, 78 | expectedResults: { 79 | error: 'Invalid value type \'[object Function] found while inspecting vertex property \'p\' in vertex write request object. Must be serializable to JSON!' 80 | } 81 | }); 82 | 83 | testDigraphParams({ 84 | testName: "Valid full request", 85 | validatorFunction: 'verifyVertexWriteRequest', 86 | validConfig: true, 87 | request: { u: "Hey, man!", p: "whatever" }, 88 | expectedResults: { 89 | result: true 90 | } 91 | }); 92 | 93 | 94 | testDigraphParams({ 95 | testName: "Missing request", 96 | validatorFunction: 'verifyEdgeReadRequest', 97 | validConfig: false, 98 | expectedResults: { 99 | error: 'Invalid value type \'[object Undefined]\' found when expecting edge read request object.' 100 | } 101 | }); 102 | 103 | testDigraphParams({ 104 | testName: "Bad 'u'", 105 | validatorFunction: 'verifyEdgeReadRequest', 106 | validConfig: false, 107 | request: {}, 108 | expectedResults: { 109 | error: 'Invalid value type \'[object Undefined]\' found looking for vertex ID string property \'u\' in edge read request object.' 110 | } 111 | }); 112 | 113 | testDigraphParams({ 114 | testName: "Bad 'v'", 115 | validatorFunction: 'verifyEdgeReadRequest', 116 | validConfig: false, 117 | request: { u: 'vertexV' }, 118 | expectedResults: { 119 | error: 'Invalid value type \'[object Undefined]\' found looking for vertex ID string property \'v\' in edge read request object.' 120 | } 121 | }); 122 | 123 | testDigraphParams({ 124 | testName: "Valid 'v'", 125 | validatorFunction: 'verifyEdgeReadRequest', 126 | validConfig: true, 127 | request: { u: 'vertexU', v: 'vertexV' }, 128 | expectedResults: { 129 | result: true 130 | } 131 | }); 132 | 133 | testDigraphParams({ 134 | testName: "Missing request", 135 | validatorFunction: 'verifyEdgeWriteRequest', 136 | validConfig: false, 137 | expectedResults: { 138 | error: 'Invalid value type \'[object Undefined]\' found when expecting edge write request object.' 139 | } 140 | }); 141 | 142 | testDigraphParams({ 143 | testName: "Bad 'e'", 144 | validatorFunction: 'verifyEdgeWriteRequest', 145 | validConfig: false, 146 | request: {}, 147 | expectedResults: { 148 | error: 'Invalid value type \'[object Undefined]\' found looking for edge descriptor object \'e\' in edge write request object.' 149 | } 150 | }); 151 | 152 | testDigraphParams({ 153 | testName: "Bad 'e.u'", 154 | validatorFunction: 'verifyEdgeWriteRequest', 155 | validConfig: false, 156 | request: { e: {} }, 157 | expectedResults: { 158 | error: 'Invalid value type \'[object Undefined]\' found looking for vertex ID string property \'e.u\' in edge write request object.' 159 | } 160 | }); 161 | 162 | testDigraphParams({ 163 | testName: "Bad 'e.v'", 164 | validatorFunction: 'verifyEdgeWriteRequest', 165 | validConfig: false, 166 | request: { e: { u: 'vertexU' } }, 167 | expectedResults: { 168 | error: 'Invalid value type \'[object Undefined]\' found looking for vertex ID string property \'e.v\' in edge write request object.' 169 | } 170 | }); 171 | 172 | 173 | testDigraphParams({ 174 | testName: "Valid 'e'", 175 | validatorFunction: 'verifyEdgeWriteRequest', 176 | validConfig: true, 177 | request: { e: { u: 'vertexU', v: 'vertexV' } }, 178 | expectedResults: { 179 | result: true 180 | } 181 | }); 182 | 183 | testDigraphParams({ 184 | testName: "Bad 'p'", 185 | validatorFunction: 'verifyEdgeWriteRequest', 186 | validConfig: false, 187 | request: { e: { u: 'vertexU', v: 'vertexV' }, p: function() {} }, 188 | expectedResults: { 189 | error: 'Invalid value type \'[object Function]\' found while insecting edge property \'p\' in edge write request object. Must be serializable to JSON!' 190 | } 191 | }); 192 | 193 | testDigraphParams({ 194 | testName: "Valid full request", 195 | validatorFunction: 'verifyEdgeWriteRequest', 196 | validConfig: true, 197 | request: { e: { u: 'vertexU', v: 'vertexV' }, p: "whatever" }, 198 | expectedResults: { 199 | result: true 200 | } 201 | }); 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/arc_core_digraph_algorithm_request.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | var helperFunctions = require('./arc_core_graph_util'); 12 | var TRAVERSE_CONTEXT = require('./arc_core_digraph_algorithm_context'); 13 | 14 | /* 15 | request = { 16 | digraph: reference to jsgraph.DirectedGraph container object (required) 17 | visitor: reference to jsgraph BFV visitor object (required) 18 | options: { 19 | startVector: reference to a vertex ID string, or an array of vertex ID strings (optional) 20 | Note: if ommitted, BFT uses the digraph's root vertex set as the start vertex set 21 | allowEmptyStartVector: Boolean flag (optional - default is false is omitted) 22 | signalStart: Boolean flag (optional - default is true if ommitted) 23 | Note: By default, BFT will call startVertex on each search root vertex. 24 | In advanced scenarios you may wish to override this behavior. 25 | traverseContext: reference to BFT search context object (optional) 26 | Note: By default, BFT allocates the traversal context internally and returns it to 27 | the caller. In advanced scenarios you may wish to provide a pre-initialized 28 | (or potentially pre-colored) traversal context object. 29 | } 30 | } 31 | } 32 | 33 | response = { 34 | error: null or string explaining why result is null 35 | result: Traversal context object or null if error 36 | } 37 | */ 38 | 39 | module.exports = function (request_) { 40 | 41 | var response = { error: null, result: null }; 42 | var errors = []; 43 | var nrequest = null; 44 | var inBreakScope = false; 45 | 46 | var createTraverseContext = function() { 47 | var response = TRAVERSE_CONTEXT({ digraph: nrequest.digraph }); 48 | var result = null; 49 | if (response.error) { 50 | errors.unshift(response.error); 51 | } else { 52 | result = response.result; 53 | } 54 | return result; 55 | }; 56 | 57 | var getRootVertices = function() { 58 | return nrequest.digraph.getRootVertices(); 59 | }; 60 | 61 | while (!inBreakScope) { 62 | inBreakScope = true; 63 | 64 | // Verify the outer shape of the request object. 65 | var innerResponse = helperFunctions.JSType(request_); 66 | if (innerResponse !== '[object Object]') { 67 | errors.unshift("Missing request object ~. Found type '" + innerResponse + "'."); 68 | break; 69 | } 70 | nrequest = {}; 71 | innerResponse = helperFunctions.JSType(request_.digraph); 72 | if (innerResponse !== '[object Object]') { 73 | errors.unshift("Missing required DirectedGraph reference ~.digraph. Found type '" + innerResponse + "'."); 74 | break; 75 | } 76 | nrequest.digraph = request_.digraph; 77 | innerResponse = helperFunctions.JSType(request_.visitor); 78 | if (innerResponse !== '[object Object]') { 79 | errors.unshift("Missing required visitor object reference ~.visitor. Found type '" + innerResponse + "'."); 80 | break; 81 | } 82 | 83 | nrequest.visitor = request_.visitor; 84 | innerResponse = helperFunctions.JSType(request_.options); 85 | if ((innerResponse !== '[object Undefined]') && (innerResponse !== '[object Object]')) { 86 | errors.unshift("Options object ~.options is the wrong type. Found type '" + innerResponse + "'."); 87 | break; 88 | } 89 | nrequest.options = {}; 90 | if (innerResponse === '[object Object]') { 91 | innerResponse = helperFunctions.JSType(request_.options.startVector); 92 | switch (innerResponse) { 93 | case '[object Undefined]': 94 | break; 95 | case '[object String]': 96 | nrequest.options.startVector = [ request_.options.startVector ]; 97 | break; 98 | case '[object Array]': 99 | nrequest.options.startVector = request_.options.startVector; 100 | break; 101 | default: 102 | errors.unshift("Options object property ~.options.startVector is the wrong type. Expected either '[object String]', '[object Array]', or '[object Undefined]'. Found type '" + innerResponse + "'."); 103 | break; 104 | } // end switch 105 | 106 | if (errors.length) { 107 | break; 108 | } 109 | 110 | innerResponse = helperFunctions.JSType(request_.options.allowEmptyStartVector); 111 | if ((innerResponse !== '[object Undefined]') && (innerResponse !== '[object Boolean]')) { 112 | errors.unshift("Options object property ~.options.allowEmptyStartVector is the wrong type. Expected either '[object Boolean]' or '[object Undefined]. Found type '" + innerResponse + "'."); 113 | break; 114 | } 115 | if (innerResponse == '[object Boolean]') { 116 | nrequest.options.allowEmptyStartVector = request_.options.allowEmptyStartVector; 117 | } 118 | 119 | innerResponse = helperFunctions.JSType(request_.options.signalStart); 120 | if ((innerResponse !== '[object Undefined]') && (innerResponse !== '[object Boolean]')) { 121 | errors.unshift("Options object property ~.options.signalStart is the wrong type. Expected either '[object Boolean]' or '[object Undefined]'. Found type '" + innerResponse + "'."); 122 | break; 123 | } 124 | if (innerResponse === '[object Boolean]') { 125 | nrequest.options.signalStart = request_.options.signalStart; 126 | } 127 | 128 | 129 | innerResponse = helperFunctions.JSType(request_.options.traverseContext); 130 | if ((innerResponse !== '[object Undefined]') && (innerResponse !== '[object Object]')) { 131 | errors.unshift("Options object property ~.options.traverseContext is the wrong type. Expected either '[object Object]' or '[object Undefined']. Found type '" + innerResponse + "'."); 132 | break; 133 | } 134 | if (innerResponse === '[object Object]') { 135 | nrequest.options.traverseContext = request_.options.traverseContext; 136 | } 137 | 138 | } // end if options object specified 139 | 140 | helperFunctions.setPropertyValueIfUndefined(nrequest.options, 'startVector', getRootVertices); 141 | helperFunctions.setPropertyValueIfUndefined(nrequest.options, 'allowEmptyStartVector', false); 142 | helperFunctions.setPropertyValueIfUndefined(nrequest.options, 'signalStart', true); 143 | helperFunctions.setPropertyValueIfUndefined(nrequest.options, 'traverseContext', createTraverseContext); 144 | 145 | // Ensure that the starting vertex set is not empty (unless allowed). 146 | if (!nrequest.options.startVector.length && !nrequest.options.allowEmptyStartVector) { 147 | errors.unshift("Traversal aborted because we don't know which vertex to start on. Specify a graph that has at least one root vertex, explicity specify the start vertex (or vertices) via `request.options.startVector` array, or suppress this error by setting `request.options.allowEmptyStartVector` to Boolean true."); 148 | break; 149 | } 150 | 151 | response.result = nrequest; 152 | 153 | } 154 | if (errors.length) { 155 | response.error = errors.join(' '); 156 | } else { 157 | response.result = nrequest; 158 | } 159 | return response; 160 | 161 | }; 162 | -------------------------------------------------------------------------------- /test/test-digraph-algorithm-common-request.js: -------------------------------------------------------------------------------- 1 | // test-digraph-algorithm-common-request.js 2 | 3 | var testModule = require('./module-under-test'); 4 | var DirectedGraph = testModule('arc_core_digraph').DirectedGraph; 5 | var createTraverseContext = testModule('arc_core_digraph_algorithm_context'); 6 | 7 | var assert = require('chai').assert; 8 | var testTraverseRequestNormalizer = require('./fixture/test-runner-algorithm-common-request'); 9 | 10 | var nullVisitor = {}; 11 | 12 | testTraverseRequestNormalizer({ 13 | testName: "Bad input: missing request", validConfig: false, 14 | expectedResults: { 15 | error: 'Missing request object ~. Found type \'[object Undefined]\'.', 16 | json: '' 17 | } 18 | }); 19 | 20 | testTraverseRequestNormalizer({ 21 | testName: "Bad input: empty request", validConfig: false, 22 | expectedResults: { 23 | error: 'Missing request object ~. Found type \'[object Undefined]\'.', 24 | json: '' 25 | } 26 | }); 27 | 28 | (function() { 29 | var digraph = new DirectedGraph({ 30 | elist: [ {e:{u:"VARMIT",v:"RAT"}}, {e:{u:"VARMIT",v:"MOUSE"}},{e:{u:"VARMIT",v:"VOLE"}}] 31 | }); 32 | 33 | testTraverseRequestNormalizer({ 34 | testName: "Bad input: missing 'visitor'", validConfig: false, 35 | request: { digraph: digraph }, 36 | expectedResults: { 37 | error: 'Missing required visitor object reference ~.visitor. Found type \'[object Undefined]\'.', 38 | json: '' 39 | } 40 | }); 41 | 42 | testTraverseRequestNormalizer({ 43 | testName: "Bad input: Bad options object type", validConfig: false, 44 | request: { digraph: digraph, visitor: nullVisitor, options: "Joe Smith" }, 45 | expectedResults: { 46 | error: 'Options object ~.options is the wrong type. Found type \'[object String]\'.', 47 | json: '' 48 | } 49 | }); 50 | 51 | testTraverseRequestNormalizer({ 52 | testName: "Minimum viable input", validConfig: true, 53 | request: { digraph: digraph, visitor: nullVisitor }, 54 | expectedResults: { 55 | error: '', 56 | json: '{"digraph":{"name":"","description":"","vlist":[],"elist":[{"e":{"u":"VARMIT","v":"RAT"}},{"e":{"u":"VARMIT","v":"MOUSE"}},{"e":{"u":"VARMIT","v":"VOLE"}}]},"visitor":{},"options":{"startVector":["VARMIT"],"allowEmptyStartVector":false,"signalStart":true,"traverseContext":{"searchStatus":"pending","colorMap":{"VARMIT":0,"RAT":0,"MOUSE":0,"VOLE":0},"undiscoveredMap":{"VARMIT":true,"RAT":true,"MOUSE":true,"VOLE":true}}}}' 57 | } 58 | }); 59 | 60 | testTraverseRequestNormalizer({ 61 | testName: "options.signalStart set explicitly true", validConfig: true, 62 | request: { digraph: digraph, visitor: nullVisitor, options: { signalStart: true }}, 63 | expectedResults: { 64 | error: '', 65 | json: '{"digraph":{"name":"","description":"","vlist":[],"elist":[{"e":{"u":"VARMIT","v":"RAT"}},{"e":{"u":"VARMIT","v":"MOUSE"}},{"e":{"u":"VARMIT","v":"VOLE"}}]},"visitor":{},"options":{"signalStart":true,"startVector":["VARMIT"],"allowEmptyStartVector":false,"traverseContext":{"searchStatus":"pending","colorMap":{"VARMIT":0,"RAT":0,"MOUSE":0,"VOLE":0},"undiscoveredMap":{"VARMIT":true,"RAT":true,"MOUSE":true,"VOLE":true}}}}' 66 | } 67 | }); 68 | 69 | testTraverseRequestNormalizer({ 70 | testName: "options.signalStart set explicitly false", validConfig: true, 71 | request: { digraph: digraph, visitor: nullVisitor, options: { signalStart: false }}, 72 | expectedResults: { 73 | error: '', 74 | json: '{"digraph":{"name":"","description":"","vlist":[],"elist":[{"e":{"u":"VARMIT","v":"RAT"}},{"e":{"u":"VARMIT","v":"MOUSE"}},{"e":{"u":"VARMIT","v":"VOLE"}}]},"visitor":{},"options":{"signalStart":false,"startVector":["VARMIT"],"allowEmptyStartVector":false,"traverseContext":{"searchStatus":"pending","colorMap":{"VARMIT":0,"RAT":0,"MOUSE":0,"VOLE":0},"undiscoveredMap":{"VARMIT":true,"RAT":true,"MOUSE":true,"VOLE":true}}}}' 75 | } 76 | }); 77 | 78 | testTraverseRequestNormalizer({ 79 | testName: "Bad input: options.startVector overridden with wrong type", validConfig: false, 80 | request: { digraph: digraph, visitor: nullVisitor, options: { startVector:{ x: "mark's the spot"}}}, 81 | expectedResults: { 82 | error: 'Options object property ~.options.startVector is the wrong type. Expected either \'[object String]\', \'[object Array]\', or \'[object Undefined]\'. Found type \'[object Object]\'.', 83 | json: '' 84 | } 85 | }); 86 | 87 | testTraverseRequestNormalizer({ 88 | testName: "options.startVector overridden with string vertex ID", validConfig: true, 89 | request: { digraph: digraph, visitor: nullVisitor, options: { startVector: "someVertexMayBeInvalid"}}, 90 | expectedResults: { 91 | error: '', 92 | json: '{"digraph":{"name":"","description":"","vlist":[],"elist":[{"e":{"u":"VARMIT","v":"RAT"}},{"e":{"u":"VARMIT","v":"MOUSE"}},{"e":{"u":"VARMIT","v":"VOLE"}}]},"visitor":{},"options":{"startVector":["someVertexMayBeInvalid"],"allowEmptyStartVector":false,"signalStart":true,"traverseContext":{"searchStatus":"pending","colorMap":{"VARMIT":0,"RAT":0,"MOUSE":0,"VOLE":0},"undiscoveredMap":{"VARMIT":true,"RAT":true,"MOUSE":true,"VOLE":true}}}}' 93 | } 94 | }); 95 | 96 | testTraverseRequestNormalizer({ 97 | testName: "options.startVector overridden with array string vertex ID", validConfig: true, 98 | request: { digraph: digraph, visitor: nullVisitor, options: { startVector: [ "someVertexMayBeInvalid", "apple", "orange", "RAT" ]}}, 99 | expectedResults: { 100 | error: '', 101 | json: '{"digraph":{"name":"","description":"","vlist":[],"elist":[{"e":{"u":"VARMIT","v":"RAT"}},{"e":{"u":"VARMIT","v":"MOUSE"}},{"e":{"u":"VARMIT","v":"VOLE"}}]},"visitor":{},"options":{"startVector":["someVertexMayBeInvalid","apple","orange","RAT"],"allowEmptyStartVector":false,"signalStart":true,"traverseContext":{"searchStatus":"pending","colorMap":{"VARMIT":0,"RAT":0,"MOUSE":0,"VOLE":0},"undiscoveredMap":{"VARMIT":true,"RAT":true,"MOUSE":true,"VOLE":true}}}}' 102 | } 103 | }); 104 | 105 | (function() { 106 | var contextResponse = createTraverseContext({ digraph: digraph }); 107 | assert.isNull(contextResponse.error); 108 | var traverseContext = contextResponse.result; 109 | traverseContext.searchStatus = "TEST-VALUE"; 110 | 111 | 112 | testTraverseRequestNormalizer({ 113 | testName: "Bad input: options.traverseContext set explicitly to wrong type", validConfig: false, 114 | request: { digraph: digraph, visitor: nullVisitor, options: { traverseContext: 'Hey, man!' }} , 115 | expectedResults: { 116 | error: 'Options object property ~.options.traverseContext is the wrong type. Expected either \'[object Object]\' or \'[object Undefined\']. Found type \'[object String]\'.', 117 | json: '' 118 | } 119 | }); 120 | 121 | testTraverseRequestNormalizer({ 122 | testName: "options.traverseContext overridden explicitly", validConfig: true, 123 | request: { digraph: digraph, visitor: nullVisitor, options: { traverseContext: traverseContext }} , 124 | expectedResults: { 125 | error: '', 126 | json: '{"digraph":{"name":"","description":"","vlist":[],"elist":[{"e":{"u":"VARMIT","v":"RAT"}},{"e":{"u":"VARMIT","v":"MOUSE"}},{"e":{"u":"VARMIT","v":"VOLE"}}]},"visitor":{},"options":{"traverseContext":{"searchStatus":"TEST-VALUE","colorMap":{"VARMIT":0,"RAT":0,"MOUSE":0,"VOLE":0},"undiscoveredMap":{"VARMIT":true,"RAT":true,"MOUSE":true,"VOLE":true}},"startVector":["VARMIT"],"allowEmptyStartVector":false,"signalStart":true}}' 127 | } 128 | }); 129 | 130 | 131 | })(); 132 | })(); 133 | 134 | 135 | (function() { 136 | 137 | var digraph = new DirectedGraph({elist:[{e:{u:'A',v:'B'}}, {e:{u:'B',v:'A'}}]}); 138 | 139 | testTraverseRequestNormalizer({ 140 | testName: "Empty start vertex set with no options.allowEmptyStartVector override", validConfig: false, 141 | request: { digraph: digraph, visitor: nullVisitor }, 142 | expectedResults: { 143 | error: 'Traversal aborted because we don\'t know which vertex to start on. Specify a graph that has at least one root vertex, explicity specify the start vertex (or vertices) via `request.options.startVector` array, or suppress this error by setting `request.options.allowEmptyStartVector` to Boolean true.', 144 | json: '' 145 | } 146 | }); 147 | 148 | testTraverseRequestNormalizer({ 149 | testName: "Empty start vertex with options.allowEmptyStartVector override set", validConfig: true, 150 | request: { digraph: digraph, visitor: nullVisitor, options: { allowEmptyStartVector: true }}, 151 | expectedResults: { 152 | error: '', 153 | json: '{"digraph":{"name":"","description":"","vlist":[],"elist":[{"e":{"u":"A","v":"B"}},{"e":{"u":"B","v":"A"}}]},"visitor":{},"options":{"allowEmptyStartVector":true,"startVector":[],"signalStart":true,"traverseContext":{"searchStatus":"pending","colorMap":{"A":0,"B":0},"undiscoveredMap":{"A":true,"B":true}}}}' 154 | } 155 | }); 156 | 157 | })(); 158 | -------------------------------------------------------------------------------- /src/arc_core_digraph_algorithm_bft.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | /* 12 | Inspired by the design of the Boost Graph Library (BGL) 13 | http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/index.html 14 | 15 | All visitor callback functions are optional. 16 | See also BFS Visitor Concept documentation from the BGL: 17 | http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/BFSVisitor.html 18 | 19 | var breadthFirstVisitorInterface = { 20 | initializeVertex: function(vertexId_, digraph_), 21 | discoverVertex: function(vertexId_, digraph_), 22 | startVertex: function(vertexId_, digraph_), 23 | examineVertex: function(vertexId_, digraph_), 24 | examineEdge: function(vertexIdU_, vertexIdV_, digraph_), 25 | treeEdge: function(vertexIdU_, vertexIdV_, digraph_), 26 | nonTreeEdge: function(vertexIdU_, vertexIdV_, digraph_), 27 | grayTarget: function(vertexIdU_, vertexIdV_, digraph_), 28 | blackTarget: function(vertexIdU_, vertexIdV_, digraph_), 29 | finishVertex: function(vertexId_, digraph_) 30 | }; 31 | 32 | request = { 33 | digraph: reference to jsgraph.DirectedGraph container object (required) 34 | visitor: reference to jsgraph BFV visitor object (required) 35 | options: { 36 | startVector: reference to a vertex ID string, or an array of vertex ID strings (optional) 37 | Note: if ommitted, BFT uses the digraph's root vertex set as the start vertex set 38 | signalStart: Boolean flag (optional - default is true if ommitted) 39 | Note: By default, BFT will call startVertex on each search root vertex. 40 | In advanced scenarios you may wish to override this behavior. 41 | traverseContext: reference to BFT search context object (optional) 42 | Note: By default, BFT allocates the traversal context internally and returns it to 43 | the caller. In advanced scenarios you may wish to provide a pre-initialized 44 | (or potentially pre-colored) traversal context object. 45 | } 46 | } 47 | } 48 | 49 | response = { 50 | error: null or string explaining why result is null 51 | result: BFS search context object 52 | } 53 | */ 54 | 55 | var algorithmName = "BFT"; // constant string used in error messages 56 | var colors = require('./arc_core_digraph_algorithm_colors'); 57 | var visitorCallback = require('./arc_core_digraph_algorithm_visit'); 58 | var normalizeRequest = require('./arc_core_digraph_algorithm_request'); 59 | 60 | 61 | module.exports = function (request_) { 62 | 63 | var nrequest = null; // normalized request object 64 | var response = { error: null, result: null }; 65 | var errors = []; 66 | var continueSearch = true; 67 | var inBreakScope = false; 68 | var searchQueue = []; 69 | 70 | while (!inBreakScope) { 71 | inBreakScope = true; 72 | var index, vertexId; 73 | 74 | var innerResponse = normalizeRequest(request_); 75 | if (innerResponse.error) { 76 | errors.unshift(innerResponse.error); 77 | break; 78 | } 79 | nrequest = innerResponse.result; 80 | 81 | // initializeVertex visitor callback. 82 | if (nrequest.options.traverseContext.searchStatus === 'pending') { 83 | for (vertexId in nrequest.options.traverseContext.colorMap) { 84 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'initializeVertex', request: { u: vertexId, g: nrequest.digraph }}); 85 | if (innerResponse.error) { 86 | errors.unshift(innerResponse.error); 87 | break; 88 | } 89 | continueSearch = innerResponse.result; 90 | if (!continueSearch) { 91 | break; 92 | } 93 | } 94 | } // if searchStatus 'pending' 95 | 96 | nrequest.options.traverseContext.searchStatus = 'active'; 97 | 98 | if (errors.length || !continueSearch) { 99 | break; 100 | } 101 | 102 | // Initialize the BF visit or search. 103 | // Note that all that distinguishes visit from search is the number of starting vertices. One -> visit, N -> search. 104 | 105 | for (index in nrequest.options.startVector) { 106 | var startingVertexId = nrequest.options.startVector[index]; 107 | // Ensure the starting vertex is in the graph container. 108 | if (!nrequest.digraph.isVertex(startingVertexId)) { 109 | errors.unshift("BFT request failed. Vertex '" + startingVertexId + "' not found in specfied directed graph container."); 110 | break; 111 | } 112 | // Ensure the vertex is white in the color map. 113 | if (nrequest.options.traverseContext.colorMap[startingVertexId] !== colors.white) { 114 | errors.unshift("BFT request failed. Vertex '" + startingVertexId + "' color map not initialized to white."); 115 | break; 116 | } 117 | 118 | // startVertex visitor callback. 119 | if (nrequest.options.signalStart) { 120 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'startVertex', request: { u: startingVertexId, g: nrequest.digraph }}); 121 | if (innerResponse.error) { 122 | errors.unshift(innerResponse.error); 123 | break; 124 | } 125 | continueSearch = innerResponse.result; 126 | } 127 | 128 | // Conditionally exit the loop if discoverVertex returned false. 129 | if (errors.length || !continueSearch) { 130 | break; 131 | } 132 | 133 | // discoverVertex visitor callback. 134 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'discoverVertex', request: { u: startingVertexId, g: nrequest.digraph }}); 135 | if (innerResponse.error) { 136 | errors.unshift(innerResponse.error); 137 | break; 138 | } 139 | continueSearch = innerResponse.result; 140 | 141 | // Remove the vertex from the undiscovered vertex map. 142 | delete nrequest.options.traverseContext.undiscoveredMap[startingVertexId]; 143 | 144 | // Add the vertex to the search 145 | searchQueue.push(startingVertexId); 146 | 147 | // Color the vertex discovered (gray) 148 | nrequest.options.traverseContext.colorMap[startingVertexId] = colors.gray; 149 | 150 | // Conditionally exit the loop if discoverVertex returned false. 151 | if (!continueSearch) { 152 | break; 153 | } 154 | 155 | } // for initialize search 156 | 157 | // Execute the main breadth-first algorithm using the starting vertex set as the initial contents of the searchQueue. 158 | while (searchQueue.length && continueSearch && !errors.length) { 159 | 160 | vertexId = searchQueue.shift(); 161 | 162 | // By convention 163 | nrequest.options.traverseContext.colorMap[vertexId] = colors.black; 164 | 165 | // examineVertex visitor callback. 166 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'examineVertex', request: { u: vertexId, g: nrequest.digraph }}); 167 | if (innerResponse.error) { 168 | errors.unshift(innerResponse.error); 169 | break; 170 | } 171 | continueSearch = innerResponse.result; 172 | if (!continueSearch) { 173 | break; 174 | } 175 | 176 | var outEdges = nrequest.digraph.outEdges(vertexId); 177 | 178 | for (index in outEdges) { 179 | 180 | var outEdge = outEdges[index]; 181 | 182 | // examineEdge visitor callback. 183 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'examineEdge', request: { e: outEdge, g: nrequest.digraph }}); 184 | if (innerResponse.error) { 185 | errors.unshift(innerResponse.error); 186 | break; 187 | } 188 | continueSearch = innerResponse.result; 189 | if (!continueSearch) { 190 | break; 191 | } 192 | 193 | var colorV = nrequest.options.traverseContext.colorMap[outEdge.v]; 194 | switch (colorV) { 195 | 196 | case colors.white: 197 | // discoverVertex visitor callback. 198 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'discoverVertex', request: { u: outEdge.v, g: nrequest.digraph }}); 199 | if (innerResponse.error) { 200 | errors.unshift(innerResponse.error); 201 | break; 202 | } 203 | continueSearch = innerResponse.result; 204 | delete nrequest.options.traverseContext.undiscoveredMap[outEdge.v]; 205 | if (!continueSearch) { 206 | break; 207 | } 208 | // treeEdge visitor callback. 209 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'treeEdge', request: { e: outEdge, g: nrequest.digraph }}); 210 | if (innerResponse.error) { 211 | errors.unshift(innerResponse.error); 212 | break; 213 | } 214 | continueSearch = innerResponse.result; 215 | searchQueue.push(outEdge.v); 216 | nrequest.options.traverseContext.colorMap[outEdge.v] = colors.gray; 217 | break; 218 | 219 | case colors.gray: 220 | // nonTreeEdge visitor callback. 221 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'nonTreeEdge', request: { e: outEdge, g: nrequest.digraph }}); 222 | if (innerResponse.error) { 223 | errors.unshift(innerResponse.error); 224 | break; 225 | } 226 | continueSearch = innerResponse.result; 227 | if (continueSearch) { 228 | // grayTarget visitor callback. 229 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'grayTarget', request: { e: outEdge, g: nrequest.digraph }}); 230 | if (innerResponse.error) { 231 | errors.unshift(innerResponse.error); 232 | break; 233 | } 234 | continueSearch = innerResponse.result; 235 | } 236 | break; 237 | 238 | case colors.black: 239 | // nonTreeEdge visitor callback. 240 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'nonTreeEdge', request: { e: outEdge, g: nrequest.digraph }}); 241 | if (innerResponse.error) { 242 | errors.unshift(innerResponse.error); 243 | break; 244 | } 245 | continueSearch = innerResponse.result; 246 | if (continueSearch) { 247 | // blackTarget visitor callback. 248 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'blackTarget', request: { e: outEdge, g: nrequest.digraph }}); 249 | if (innerResponse.error) { 250 | errors.unshift(innerResponse.error); 251 | break; 252 | } 253 | continueSearch = innerResponse.result; 254 | } 255 | break; 256 | 257 | default: 258 | errors.unshift("BFT failure: An invalid color value was found in the color map for vertex '" + outEdge.v + "'. Please file an issue!"); 259 | break; 260 | 261 | } // switch (colorV) 262 | 263 | if (errors.length || !continueSearch) { 264 | break; 265 | } 266 | 267 | } // for (outEdge in outEdges) 268 | 269 | if (errors.length || !continueSearch) { 270 | break; 271 | } 272 | 273 | // finishVertex visitor callback. 274 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'finishVertex', request: { u: vertexId, g: nrequest.digraph }}); 275 | if (innerResponse.error) { 276 | errors.unshift(innerResponse.error); 277 | break; 278 | } 279 | continueSearch = innerResponse.result; 280 | if (!continueSearch) { 281 | break; 282 | } 283 | 284 | } // while (searchQueue.length) 285 | 286 | } // end while (!inBreakScope) 287 | 288 | if (errors.length) { 289 | if (nrequest) { 290 | nrequest.options.traverseContext.searchStatus = 'error'; 291 | } 292 | errors.unshift("jsgraph.directed.breadthFirstTraverse algorithm failure:"); 293 | response.error = errors.join(' '); 294 | } else { 295 | nrequest.options.traverseContext.searchStatus = continueSearch?'completed':'terminated'; 296 | response.result = nrequest.options.traverseContext; 297 | } 298 | return response; 299 | }; 300 | -------------------------------------------------------------------------------- /src/arc_core_digraph_algorithm_dft.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | var algorithmName = "DFT"; // used in error messages 12 | var colors = require('./arc_core_digraph_algorithm_colors'); 13 | var visitorCallback = require('./arc_core_digraph_algorithm_visit'); 14 | var normalizeRequest = require('./arc_core_digraph_algorithm_request'); 15 | 16 | 17 | module.exports = function (request_) { 18 | 19 | var nrequest = null; // normalized request 20 | var response = { error: null, result: null }; 21 | var errors = []; 22 | var continueSearch = true; 23 | var inBreakScope = false; 24 | 25 | while (!inBreakScope) { 26 | inBreakScope = true; 27 | var index, vertexId; 28 | var finishedEdges = {}; 29 | var innerRequest = null; 30 | var hash = null; 31 | 32 | var innerResponse = normalizeRequest(request_); 33 | if (innerResponse.error) { 34 | errors.unshift(innerResponse.error); 35 | break; 36 | } 37 | nrequest = innerResponse.result; 38 | 39 | // initializeVertex visitor callback. 40 | if (nrequest.options.traverseContext.searchStatus === 'pending') { 41 | for (vertexId in nrequest.options.traverseContext.colorMap) { 42 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'initializeVertex', request: { u: vertexId, g: nrequest.digraph }}); 43 | if (innerResponse.error) { 44 | errors.unshift(innerResponse.error); 45 | break; 46 | } 47 | continueSearch = innerResponse.result; 48 | if (!continueSearch) { 49 | break; 50 | } 51 | } // end for 52 | } // if searchStatus 'pending' 53 | 54 | nrequest.options.traverseContext.searchStatus = 'active'; 55 | 56 | if (errors.length || !continueSearch) { 57 | break; 58 | } 59 | 60 | // Outer depth-first search loop iterates over the start vertex set. 61 | for (index in nrequest.options.startVector) { 62 | 63 | vertexId = nrequest.options.startVector[index]; 64 | 65 | // Ensure the starting vertex is actually in the graph. 66 | if (!nrequest.digraph.isVertex(vertexId)) { 67 | errors.unshift("DFT request failed. Vertex '" + vertexId + "' not found in specified directed graph container."); 68 | break; 69 | } 70 | 71 | // Ensure the starting vertex is undicovered (white in the color map). 72 | if (nrequest.options.traverseContext.colorMap[vertexId] !== colors.white) { 73 | errors.unshift("DFT request failed. Vertex '" + vertexId + "' color map not initialized to white."); 74 | break; 75 | } 76 | 77 | // startVertex visitor callback 78 | if (nrequest.options.signalStart) { 79 | innerResponse = visitorCallback({ algorithm: algorithmName, visitor: nrequest.visitor, method: 'startVertex', request: { u: vertexId, g: nrequest.digraph }}); 80 | if (innerResponse.error) { 81 | errors.unshift(innerResponse.error); 82 | break; 83 | } 84 | continueSearch = innerResponse.result; 85 | } 86 | if (!continueSearch) { 87 | break; 88 | } 89 | 90 | // searchStack is a FILO of FIFO's (or stack of queues if you prefer) 91 | // initialized with starting vertex set member under-evaluation's ID. 92 | var searchStack = [ [ vertexId ] ]; 93 | 94 | // Iterate until search stack is empty, a client visitor method returns false, or an error occurs. 95 | while (searchStack.length && continueSearch && !errors.length) { 96 | 97 | // Peek at the identifier of the vertex at the front of the queue atop the search stack. 98 | 99 | var currentVertexId = (searchStack[searchStack.length - 1])[0]; 100 | 101 | switch (nrequest.options.traverseContext.colorMap[currentVertexId]) { 102 | 103 | case colors.white: 104 | 105 | // Remove the vertex from the undiscovered map. 106 | delete nrequest.options.traverseContext.undiscoveredMap[currentVertexId]; 107 | 108 | // Change the vertex's state to GRAY to record its discovery. 109 | nrequest.options.traverseContext.colorMap[currentVertexId] = colors.gray; 110 | 111 | // discoverVertex visitor callback. 112 | innerResponse = visitorCallback({ 113 | algorithm: algorithmName, 114 | visitor: nrequest.visitor, 115 | method: 'discoverVertex', 116 | request: { u: currentVertexId, g: nrequest.digraph } 117 | }); 118 | if (innerResponse.error) { 119 | errors.unshift(innerResponse.error); 120 | break; 121 | } 122 | continueSearch = innerResponse.result; 123 | if (!continueSearch) { 124 | break; 125 | } 126 | 127 | // treeEdge visitor callback. 128 | if (searchStack.length > 1) { 129 | innerResponse = visitorCallback({ 130 | algorithm: algorithmName, 131 | visitor: nrequest.visitor, 132 | method: 'treeEdge', 133 | request: { e: { u: searchStack[searchStack.length - 2][0], v: currentVertexId }, g: nrequest.digraph } 134 | }); 135 | if (innerResponse.error) { 136 | errors.unshift(innerResponse.error); 137 | break; 138 | } else { 139 | continueSearch = innerResponse.result; 140 | if (!continueSearch) { 141 | break; 142 | } 143 | } 144 | } 145 | 146 | // Examine adjacent vertices 147 | var vertexOutEdges = nrequest.digraph.outEdges(currentVertexId); 148 | var adjacentVertices = []; 149 | 150 | while (vertexOutEdges.length && !errors.length && continueSearch) { 151 | 152 | var adjacentVertexId = vertexOutEdges.shift().v; 153 | 154 | // examineEdge visitor callback. 155 | innerResponse = visitorCallback({ 156 | algorithm: algorithmName, 157 | visitor: nrequest.visitor, 158 | method: 'examineEdge', 159 | request: { e: { u: currentVertexId, v: adjacentVertexId }, g: nrequest.digraph } 160 | }); 161 | if (innerResponse.error) { 162 | errors.unshift(innerRepsonse.error); 163 | break; 164 | } 165 | continueSearch = innerResponse.result; 166 | if (!continueSearch) { 167 | break; 168 | } 169 | 170 | switch (nrequest.options.traverseContext.colorMap[adjacentVertexId]) { 171 | 172 | case colors.white: 173 | adjacentVertices.push(adjacentVertexId); 174 | break; 175 | case colors.gray: 176 | // backEdge visitor callback. 177 | innerResponse = visitorCallback({ 178 | algorithm: algorithmName, 179 | visitor: nrequest.visitor, 180 | method: 'backEdge', 181 | request: { e: { u: currentVertexId, v: adjacentVertexId }, g: nrequest.digraph } 182 | }); 183 | if (innerResponse.error) { 184 | errors.unshift(innerResponse.error); 185 | } else { 186 | continueSearch = innerResponse.result; 187 | } 188 | break; 189 | case colors.black: 190 | // forwardOrCrossEdge visitor callback. 191 | innerResponse = visitorCallback({ 192 | algorithm: algorithmName, 193 | visitor: nrequest.visitor, 194 | method: 'forwardOrCrossEdge', 195 | request: { e: { u: currentVertexId, v: adjacentVertexId }, g: nrequest.digraph } 196 | }); 197 | if (innerResponse.error) { 198 | errors.unshift(innerResponse.error); 199 | } else { 200 | continueSearch = innerResponse.result; 201 | } 202 | break; 203 | } 204 | } 205 | if (adjacentVertices.length) { 206 | searchStack.push(adjacentVertices); 207 | } 208 | 209 | break; 210 | 211 | case colors.gray: 212 | // change the vertex's state to black to indicate search completion. 213 | nrequest.options.traverseContext.colorMap[currentVertexId] = colors.black; 214 | // finishVertex visitor callback. 215 | innerResponse = visitorCallback({ 216 | algorithm: algorithmName, 217 | visitor: nrequest.visitor, 218 | method: 'finishVertex', 219 | request: { u: currentVertexId, g: nrequest.digraph } 220 | }); 221 | if (innerResponse.error) { 222 | errors.unshift(innerResponse.error); 223 | break; 224 | } 225 | continueSearch = innerResponse.result; 226 | if (!continueSearch) { 227 | break; 228 | } 229 | var inEdgeSet = nrequest.digraph.inEdges(currentVertexId); 230 | while (inEdgeSet.length) { 231 | var inEdge = inEdgeSet.pop(); 232 | hash = inEdge.u + inEdge.v; 233 | finishedEdges[hash] = inEdge; 234 | } 235 | searchStack[searchStack.length - 1].shift(); 236 | if (!(searchStack[searchStack.length - 1].length)) { 237 | searchStack.pop(); 238 | } 239 | break; 240 | 241 | case colors.black: 242 | 243 | // The black sheep. The only way for a vertex to end up in this state 244 | // is for it to be queued after another adjacent vertex that reaches 245 | // it first in the depth-first search tree. By definition it's already 246 | // been 'finished'. 247 | 248 | if (searchStack.length > 1) { 249 | innerRequest = { e: { u: (searchStack[searchStack.length - 2])[0], v: currentVertexId }, g: nrequest.digraph }; 250 | innerResponse = visitorCallback({ 251 | algorithm: algorithmName, 252 | visitor: nrequest.visitor, 253 | method: 'forwardOrCrossEdge', 254 | request: innerRequest 255 | }); 256 | if (innerResponse.error) { 257 | errors.unshift(innerResponse.error); 258 | break; 259 | } 260 | continueSearch = innerResponse.result; 261 | if (!continueSearch) { 262 | break; 263 | } 264 | } 265 | searchStack[searchStack.length - 1].shift(); 266 | if (!searchStack[searchStack.length - 1].length) { 267 | searchStack.pop(); 268 | } 269 | break; 270 | 271 | default: 272 | errors.unshift("DFT failure: An invalid color value was found in the color map for vertex '" + currentVertexId + "'."); 273 | break; 274 | } 275 | } // while search stack is not empty 276 | 277 | if (errors.length || !continueSearch) { 278 | break; 279 | } 280 | 281 | } // end while outer depth-first search loop 282 | 283 | if (errors.length || !continueSearch) { 284 | break; 285 | } 286 | 287 | for (hash in finishedEdges) { 288 | innerRequest = { e: finishedEdges[hash], g: nrequest.digraph }; 289 | innerResponse = visitorCallback({ 290 | algorithm: algorithmName, 291 | visitor: nrequest.visitor, 292 | method: 'finishEdge', 293 | request: innerRequest 294 | }); 295 | if (innerResponse.error) { 296 | errors.unshift(innerResponse.error); 297 | break; 298 | } 299 | continueSearch = innerResponse.result; 300 | if (!continueSearch) { 301 | break; 302 | } 303 | 304 | } // end for 305 | 306 | 307 | } // while !inBreakScope 308 | 309 | if (errors.length) { 310 | if (nrequest) { 311 | nrequest.options.traverseContext.searchStatus = 'error'; 312 | } 313 | errors.unshift("jsgraph.directed.depthFirstTraverse algorithm failure:"); 314 | response.error = errors.join(' '); 315 | } else { 316 | nrequest.options.traverseContext.searchStatus = continueSearch?'completed':'terminated'; 317 | response.result = nrequest.options.traverseContext; 318 | } 319 | return response; 320 | 321 | 322 | }; 323 | 324 | -------------------------------------------------------------------------------- /test/test-digraph-algorithm-dft.js: -------------------------------------------------------------------------------- 1 | // Encapsule/jsgraph/test/test-digraph-algorithm-dft.js 2 | 3 | 4 | var assert = require('chai').assert; 5 | var testModule = require('./module-under-test'); 6 | var DirectedGraph = testModule('arc_core_digraph').DirectedGraph; 7 | var testDFT = require('./fixture/test-runner-digraph-algorithm-dft'); 8 | 9 | 10 | 11 | testDFT({ testName: "Missing request", validConfig: false, 12 | expectedResults: { 13 | error: 'jsgraph.directed.depthFirstTraverse algorithm failure: Missing request object ~. Found type \'[object Undefined]\'.', 14 | result: null, 15 | path: null 16 | }}); 17 | 18 | testDFT({ testName: "Bad request type", validConfig: false, 19 | request: "No good", 20 | expectedResults: { 21 | error: 'jsgraph.directed.depthFirstTraverse algorithm failure: Missing request object ~. Found type \'[object String]\'.', 22 | result: null, 23 | path: null 24 | }}); 25 | 26 | testDFT({ testName: "Empty request", validConfig: false, 27 | request: {}, // also no good 28 | expectedResults: { 29 | error: 'jsgraph.directed.depthFirstTraverse algorithm failure: Missing required DirectedGraph reference ~.digraph. Found type \'[object Undefined]\'.', 30 | result: null, 31 | path: null 32 | }}); 33 | 34 | (function() { 35 | var digraph = new DirectedGraph(); 36 | testDFT({ testName: "Empty digraph", validConfig: true, 37 | request: { digraph: digraph, options: { allowEmptyStartVector: true }}, 38 | expectedResults: { 39 | error: '', 40 | result: '{"searchStatus":"completed","colorMap":{},"undiscoveredMap":{}}', 41 | path: '[]' 42 | }}); 43 | })(); 44 | 45 | (function() { 46 | var digraph = new DirectedGraph(); 47 | digraph.addVertex({ u: "lone-wolf-vertex" }); 48 | testDFT({ testName: "Single vertex, default starting vertex set (initializeVertex, startVertex, discoverVertex, finishVertex test)", validConfig: true, 49 | request: { digraph: digraph }, 50 | expectedResults: { 51 | error: '', 52 | result: '{"searchStatus":"completed","colorMap":{"lone-wolf-vertex":2},"undiscoveredMap":{}}', 53 | path: '["0 initializeVertex lone-wolf-vertex","1 startVertex lone-wolf-vertex","2 discoverVertex lone-wolf-vertex at time 1","3 finishVertex lone-wolf-vertex at time 2"]' 54 | }}); 55 | })(); 56 | 57 | (function() { 58 | var digraph = new DirectedGraph(); 59 | digraph.addVertex({ u: "lone-wolf-vertex" }); 60 | testDFT({ testName: "Single vertex, starting vertex not in the graph", validConfig: false, 61 | request: { digraph: digraph, options: { startVector: 'orange'}}, 62 | expectedResults: { 63 | error: 'jsgraph.directed.depthFirstTraverse algorithm failure: DFT request failed. Vertex \'orange\' not found in specified directed graph container.', 64 | result: '', 65 | path: '' 66 | }}); 67 | })(); 68 | 69 | (function() { 70 | var digraph = new DirectedGraph(); 71 | digraph.addVertex({ u: "lone-wolf-vertex" }); 72 | testDFT({ testName: "Single vertex, starting vertex specified explicity in request", validConfig: true, 73 | request: { digraph: digraph, options: { startVector: 'lone-wolf-vertex'}}, 74 | expectedResults: { 75 | error: '', 76 | result: '{"searchStatus":"completed","colorMap":{"lone-wolf-vertex":2},"undiscoveredMap":{}}', 77 | path: '["0 initializeVertex lone-wolf-vertex","1 startVertex lone-wolf-vertex","2 discoverVertex lone-wolf-vertex at time 1","3 finishVertex lone-wolf-vertex at time 2"]' 78 | }}); 79 | })(); 80 | 81 | (function() { 82 | var digraph = new DirectedGraph(); 83 | digraph.addEdge({ e: { u: "lone-wolf-vertex", v: "lone-wolf-vertex" }}); 84 | testDFT({ testName: "Single vertex, with an out-edge to itself", validConfig: true, 85 | request: { digraph: digraph, options: { startVector: 'lone-wolf-vertex'}}, 86 | expectedResults: { 87 | error: '', 88 | result: '{"searchStatus":"completed","colorMap":{"lone-wolf-vertex":2},"undiscoveredMap":{}}', 89 | path: '["0 initializeVertex lone-wolf-vertex","1 startVertex lone-wolf-vertex","2 discoverVertex lone-wolf-vertex at time 1","3 examineEdge [lone-wolf-vertex,lone-wolf-vertex]","4 backEdge [lone-wolf-vertex,lone-wolf-vertex]","5 finishVertex lone-wolf-vertex at time 2","6 finishEdge [lone-wolf-vertex,lone-wolf-vertex]"]' 90 | }}); 91 | })(); 92 | 93 | 94 | 95 | 96 | (function() { 97 | var digraph = new DirectedGraph({ 98 | elist: [ { e: { u: 'parent', v: 'child' } } ] 99 | }); 100 | 101 | testDFT({ testName: "Two connected vertices, (treeEdge test)", validConfig: true, 102 | request: { digraph: digraph }, 103 | expectedResults: { 104 | error: '', 105 | result: '{"searchStatus":"completed","colorMap":{"parent":2,"child":2},"undiscoveredMap":{}}', 106 | path: '["0 initializeVertex parent","1 initializeVertex child","2 startVertex parent","3 discoverVertex parent at time 1","4 examineEdge [parent,child]","5 discoverVertex child at time 2","6 treeEdge [parent,child]","7 finishVertex child at time 3","8 finishVertex parent at time 4","9 finishEdge [parent,child]"]' 107 | }}); 108 | })(); 109 | 110 | (function() { 111 | var digraph = new DirectedGraph({ 112 | elist: [ 113 | { e: { u: "A", v: "B" }}, 114 | { e: { u: "B", v: "A" }} 115 | ] 116 | }); 117 | testDFT({ testName: "Two inter-connected vertices, (no starting vertex set)", validConfig: true, 118 | request: { digraph: digraph, options: { allowEmptyStartVector: true }}, 119 | expectedResults: { 120 | error: '', 121 | result: '{"searchStatus":"completed","colorMap":{"A":0,"B":0},"undiscoveredMap":{"A":true,"B":true}}', 122 | path: '["0 initializeVertex A","1 initializeVertex B"]' 123 | }}); 124 | })(); 125 | 126 | (function() { 127 | var digraph = new DirectedGraph({ 128 | elist: [ 129 | { e: { u: "A", v: "B" }}, 130 | { e: { u: "B", v: "A" }} 131 | ] 132 | }); 133 | testDFT({ testName: "Two inter-connected vertices, (backEdge test)", validConfig: true, 134 | request: { digraph: digraph, options: { startVector: "A" } }, 135 | expectedResults: { 136 | error: '', 137 | result: '{"searchStatus":"completed","colorMap":{"A":2,"B":2},"undiscoveredMap":{}}', 138 | path: '["0 initializeVertex A","1 initializeVertex B","2 startVertex A","3 discoverVertex A at time 1","4 examineEdge [A,B]","5 discoverVertex B at time 2","6 treeEdge [A,B]","7 examineEdge [B,A]","8 backEdge [B,A]","9 finishVertex B at time 3","10 finishVertex A at time 4","11 finishEdge [A,B]","12 finishEdge [B,A]"]' 139 | }}); 140 | })(); 141 | 142 | (function() { 143 | var digraph = new DirectedGraph({ 144 | elist: [ 145 | { e: { u: "A", v: "B" }}, 146 | { e: { u: "B", v: "C" }}, 147 | { e: { u: "A", v: "C" }} 148 | ] 149 | }); 150 | testDFT({ testName: "Three vertices, (forwardEdge test)", validConfig: true, 151 | request: { digraph: digraph, options: { startVector: "A" } }, 152 | expectedResults: { 153 | error: '', 154 | result: '{"searchStatus":"completed","colorMap":{"A":2,"B":2,"C":2},"undiscoveredMap":{}}', 155 | path: '["0 initializeVertex A","1 initializeVertex B","2 initializeVertex C","3 startVertex A","4 discoverVertex A at time 1","5 examineEdge [A,B]","6 examineEdge [A,C]","7 discoverVertex B at time 2","8 treeEdge [A,B]","9 examineEdge [B,C]","10 discoverVertex C at time 3","11 treeEdge [B,C]","12 finishVertex C at time 4","13 finishVertex B at time 5","14 forwardOrCrossEdge [A,C]","15 finishVertex A at time 6","16 finishEdge [A,C]","17 finishEdge [B,C]","18 finishEdge [A,B]"]' 156 | }}); 157 | })(); 158 | 159 | (function() { 160 | var digraph = new DirectedGraph({ 161 | elist: [ 162 | { e: { u: "A", v: "B" }}, 163 | { e: { u: "C", v: "B" }} 164 | ] 165 | }); 166 | testDFT({ testName: "Three vertices (crossEdge test)", validConfig: true, 167 | request: { digraph: digraph, options: { startVector: [ "A", "C"] } }, 168 | expectedResults: { 169 | error: '', 170 | result: '{"searchStatus":"completed","colorMap":{"A":2,"B":2,"C":2},"undiscoveredMap":{}}', 171 | path: '["0 initializeVertex A","1 initializeVertex B","2 initializeVertex C","3 startVertex A","4 discoverVertex A at time 1","5 examineEdge [A,B]","6 discoverVertex B at time 2","7 treeEdge [A,B]","8 finishVertex B at time 3","9 finishVertex A at time 4","10 startVertex C","11 discoverVertex C at time 5","12 examineEdge [C,B]","13 forwardOrCrossEdge [C,B]","14 finishVertex C at time 6","15 finishEdge [C,B]","16 finishEdge [A,B]"]' 172 | }}); 173 | })(); 174 | 175 | (function() { 176 | // This example is taken from section 22.3 (p. 605) of "Introduction to Algorithms" 177 | var digraph = new DirectedGraph({ 178 | elist: [ 179 | { e: { u: 'u', v: 'v' } }, 180 | { e: { u: 'v', v: 'y' } }, 181 | { e: { u: 'y', v: 'x' } }, 182 | { e: { u: 'x', v: 'v' } }, 183 | { e: { u: 'u', v: 'x' } }, 184 | { e: { u: 'w', v: 'y' } }, 185 | { e: { u: 'w', v: 'z' } }, 186 | { e: { u: 'z', v: 'z' } } 187 | ] 188 | }); 189 | testDFT({ testName: "Intro to Algorithms Figure 23.4 path classification test", validConfig: true, 190 | request: { digraph: digraph, options: { startVector: [ "w", "u"] } }, 191 | expectedResults: { 192 | error: '', 193 | result: '{"searchStatus":"completed","colorMap":{"u":2,"v":2,"y":2,"x":2,"w":2,"z":2},"undiscoveredMap":{}}', 194 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex w","7 discoverVertex w at time 1","8 examineEdge [w,y]","9 examineEdge [w,z]","10 discoverVertex y at time 2","11 treeEdge [w,y]","12 examineEdge [y,x]","13 discoverVertex x at time 3","14 treeEdge [y,x]","15 examineEdge [x,v]","16 discoverVertex v at time 4","17 treeEdge [x,v]","18 examineEdge [v,y]","19 backEdge [v,y]","20 finishVertex v at time 5","21 finishVertex x at time 6","22 finishVertex y at time 7","23 discoverVertex z at time 8","24 treeEdge [w,z]","25 examineEdge [z,z]","26 backEdge [z,z]","27 finishVertex z at time 9","28 finishVertex w at time 10","29 startVertex u","30 discoverVertex u at time 11","31 examineEdge [u,v]","32 forwardOrCrossEdge [u,v]","33 examineEdge [u,x]","34 forwardOrCrossEdge [u,x]","35 finishVertex u at time 12","36 finishEdge [x,v]","37 finishEdge [u,v]","38 finishEdge [u,x]","39 finishEdge [y,x]","40 finishEdge [w,y]","41 finishEdge [v,y]","42 finishEdge [z,z]","43 finishEdge [w,z]"]' 195 | }}); 196 | 197 | 198 | describe("Depth-first traverse termination tests.", function() { 199 | 200 | testDFT({ 201 | testName: "Depth-first terminate on 'initializeVertex'", validConfig: true, 202 | request: { 203 | digraph: digraph, 204 | visitor: { 205 | initializeVertex: function (request_) { 206 | return (request_.u !== 'w'); 207 | } 208 | } 209 | }, 210 | expectedResults: { 211 | error: '', 212 | result: '{"searchStatus":"terminated","colorMap":{"u":0,"v":0,"y":0,"x":0,"w":0,"z":0},"undiscoveredMap":{"u":true,"v":true,"y":true,"x":true,"w":true,"z":true}}', 213 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w"]' 214 | } 215 | }); 216 | 217 | testDFT({ 218 | testName: "Depth-first terminate on 'startVertex'", validConfig: true, 219 | request: { 220 | digraph: digraph, 221 | visitor: { 222 | startVertex: function (request_) { 223 | return (request_.u !== 'w'); 224 | } 225 | } 226 | }, 227 | expectedResults: { 228 | error: '', 229 | result: '{"searchStatus":"terminated","colorMap":{"u":2,"v":2,"y":2,"x":2,"w":0,"z":0},"undiscoveredMap":{"w":true,"z":true}}', 230 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]","9 examineEdge [u,x]","10 discoverVertex v at time 2","11 treeEdge [u,v]","12 examineEdge [v,y]","13 discoverVertex y at time 3","14 treeEdge [v,y]","15 examineEdge [y,x]","16 discoverVertex x at time 4","17 treeEdge [y,x]","18 examineEdge [x,v]","19 backEdge [x,v]","20 finishVertex x at time 5","21 finishVertex y at time 6","22 finishVertex v at time 7","23 forwardOrCrossEdge [u,x]","24 finishVertex u at time 8","25 startVertex w"]' 231 | } 232 | }); 233 | 234 | testDFT({ 235 | testName: "Depth-first terminate on 'discoverVertex'", validConfig: true, 236 | request: { 237 | digraph: digraph, 238 | visitor: { 239 | discoverVertex: function (request_) { 240 | return (request_.u !== 'y'); 241 | } 242 | } 243 | }, 244 | expectedResults: { 245 | error: '', 246 | result: '{"searchStatus":"terminated","colorMap":{"u":1,"v":1,"y":1,"x":0,"w":0,"z":0},"undiscoveredMap":{"x":true,"w":true,"z":true}}', 247 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]","9 examineEdge [u,x]","10 discoverVertex v at time 2","11 treeEdge [u,v]","12 examineEdge [v,y]","13 discoverVertex y at time 3"]' 248 | } 249 | }); 250 | 251 | testDFT({ 252 | testName: "Depth-first terminate on 'examineEdge'", validConfig: true, 253 | request: { 254 | digraph: digraph, 255 | visitor: { 256 | examineEdge: function (request_) { 257 | return (request_.e.v !== 'v'); 258 | } 259 | } 260 | }, 261 | expectedResults: { 262 | error: '', 263 | result: '{"searchStatus":"terminated","colorMap":{"u":1,"v":0,"y":0,"x":0,"w":0,"z":0},"undiscoveredMap":{"v":true,"y":true,"x":true,"w":true,"z":true}}', 264 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]"]' 265 | } 266 | }); 267 | 268 | 269 | testDFT({ 270 | testName: "Depth-first terminate on 'treeEdge'", validConfig: true, 271 | request: { 272 | digraph: digraph, 273 | visitor: { 274 | examineEdge: function (request_) { 275 | return (request_.e.v !== 'v'); 276 | } 277 | } 278 | }, 279 | expectedResults: { 280 | error: '', 281 | result: '{"searchStatus":"terminated","colorMap":{"u":1,"v":0,"y":0,"x":0,"w":0,"z":0},"undiscoveredMap":{"v":true,"y":true,"x":true,"w":true,"z":true}}', 282 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]"]' 283 | } 284 | }); 285 | 286 | testDFT({ 287 | testName: "Depth-first terminate on 'treeEdge'", validConfig: true, 288 | request: { 289 | digraph: digraph, 290 | visitor: { 291 | treeEdge: function (request_) { 292 | return (request_.e.v !== 'v'); 293 | } 294 | } 295 | }, 296 | expectedResults: { 297 | error: '', 298 | result: '{"searchStatus":"terminated","colorMap":{"u":1,"v":1,"y":0,"x":0,"w":0,"z":0},"undiscoveredMap":{"y":true,"x":true,"w":true,"z":true}}', 299 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]","9 examineEdge [u,x]","10 discoverVertex v at time 2","11 treeEdge [u,v]"]' 300 | } 301 | }); 302 | 303 | testDFT({ 304 | testName: "Depth-first terminate on 'backEdge'", validConfig: true, 305 | request: { 306 | digraph: digraph, 307 | visitor: { 308 | backEdge: function (request_) { 309 | return (request_.e.v !== 'v'); 310 | } 311 | } 312 | }, 313 | expectedResults: { 314 | error: '', 315 | result: '{"searchStatus":"terminated","colorMap":{"u":1,"v":1,"y":1,"x":1,"w":0,"z":0},"undiscoveredMap":{"w":true,"z":true}}', 316 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]","9 examineEdge [u,x]","10 discoverVertex v at time 2","11 treeEdge [u,v]","12 examineEdge [v,y]","13 discoverVertex y at time 3","14 treeEdge [v,y]","15 examineEdge [y,x]","16 discoverVertex x at time 4","17 treeEdge [y,x]","18 examineEdge [x,v]","19 backEdge [x,v]"]' 317 | } 318 | }); 319 | 320 | testDFT({ 321 | testName: "Depth-first terminate on 'forwardOrCrossEdge'", validConfig: true, 322 | request: { 323 | digraph: digraph, 324 | visitor: { 325 | forwardOrCrossEdge: function (request_) { 326 | return (request_.e.v !== 'x'); 327 | } 328 | } 329 | }, 330 | expectedResults: { 331 | error: '', 332 | result: '{"searchStatus":"terminated","colorMap":{"u":1,"v":2,"y":2,"x":2,"w":0,"z":0},"undiscoveredMap":{"w":true,"z":true}}', 333 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]","9 examineEdge [u,x]","10 discoverVertex v at time 2","11 treeEdge [u,v]","12 examineEdge [v,y]","13 discoverVertex y at time 3","14 treeEdge [v,y]","15 examineEdge [y,x]","16 discoverVertex x at time 4","17 treeEdge [y,x]","18 examineEdge [x,v]","19 backEdge [x,v]","20 finishVertex x at time 5","21 finishVertex y at time 6","22 finishVertex v at time 7","23 forwardOrCrossEdge [u,x]"]' 334 | } 335 | }); 336 | 337 | testDFT({ 338 | testName: "Depth-first terminate on 'finishVertex'", validConfig: true, 339 | request: { 340 | digraph: digraph, 341 | visitor: { 342 | finishVertex: function (request_) { 343 | return (request_.u !== 'y'); 344 | } 345 | } 346 | }, 347 | expectedResults: { 348 | error: '', 349 | result: '{"searchStatus":"terminated","colorMap":{"u":1,"v":1,"y":2,"x":2,"w":0,"z":0},"undiscoveredMap":{"w":true,"z":true}}', 350 | path: '["0 initializeVertex u","1 initializeVertex v","2 initializeVertex y","3 initializeVertex x","4 initializeVertex w","5 initializeVertex z","6 startVertex u","7 discoverVertex u at time 1","8 examineEdge [u,v]","9 examineEdge [u,x]","10 discoverVertex v at time 2","11 treeEdge [u,v]","12 examineEdge [v,y]","13 discoverVertex y at time 3","14 treeEdge [v,y]","15 examineEdge [y,x]","16 discoverVertex x at time 4","17 treeEdge [y,x]","18 examineEdge [x,v]","19 backEdge [x,v]","20 finishVertex x at time 5","21 finishVertex y at time 6"]' 351 | } 352 | }); 353 | 354 | }); 355 | 356 | })(); 357 | 358 | -------------------------------------------------------------------------------- /src/arc_core_digraph.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014-2016 Christopher D. Russell 3 | 4 | This library is published under the MIT License and is part of the 5 | Encapsule Project System in Cloud (SiC) open service architecture. 6 | Please follow https://twitter.com/Encapsule for news and updates 7 | about jsgraph and other time saving libraries that do amazing things 8 | with in-memory data on Node.js and HTML. 9 | */ 10 | 11 | // Inspired by the Boost Graph Library (BGL) 12 | // http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/index.html 13 | // http://en.wikipedia.org/wiki/Directed_graph 14 | 15 | var helperFunctions = require('./arc_core_graph_util'); 16 | var digraphParams = require('./arc_core_digraph_in_params'); 17 | var digraphImport = require('./arc_core_digraph_import'); 18 | var digraphExport = require('./arc_core_digraph_export'); 19 | 20 | (function() { 21 | var __bind = function(method, scope){ return function(){ return method.apply(scope, arguments); }; }; 22 | 23 | var DirectedGraph = (function() { 24 | function DirectedGraph(jsonOrObject_) { 25 | 26 | // Meta methods 27 | this.getGraphName = __bind(this.getGraphName, this); 28 | this.setGraphName = __bind(this.setGraphName, this); 29 | this.getGraphDescription = __bind(this.getGraphDescription, this); 30 | this.setGraphDescription = __bind(this.setGraphDescription, this); 31 | 32 | // Vertex-scope methods 33 | this.isVertex = __bind(this.isVertex, this); 34 | this.addVertex = __bind(this.addVertex, this); 35 | this.removeVertex = __bind(this.removeVertex, this); 36 | this.getVertexProperty = __bind(this.getVertexProperty, this); 37 | this.setVertexProperty = __bind(this.setVertexProperty, this); 38 | this.hasVertexProperty = __bind(this.hasVertexProperty, this); 39 | this.clearVertexProperty = __bind(this.clearVertexProperty, this); 40 | this.inDegree = __bind(this.inDegree, this); 41 | this.inEdges = __bind(this.inEdges, this); 42 | this.outDegree = __bind(this.outDegree, this); 43 | this.outEdges = __bind(this.outEdges, this); 44 | 45 | // Edge-scope methods 46 | this.isEdge = __bind(this.isEdge, this); 47 | this.addEdge = __bind(this.addEdge, this); 48 | this.removeEdge = __bind(this.removeEdge, this); 49 | this.getEdgeProperty = __bind(this.getEdgeProperty, this); 50 | this.setEdgeProperty = __bind(this.setEdgeProperty, this); 51 | this.hasEdgeProperty = __bind(this.hasEdgeProperty, this); 52 | this.clearEdgeProperty = __bind(this.clearEdgeProperty, this); 53 | 54 | // Digraph-scope methods 55 | this.verticesCount = __bind(this.verticesCount, this); 56 | this.getVertices = __bind(this.getVertices, this); 57 | this.edgesCount = __bind(this.edgesCount, this); 58 | this.getEdges = __bind(this.getEdges, this); 59 | this.rootVerticesCount = __bind(this.rootVerticesCount, this); 60 | this.getRootVertices = __bind(this.getRootVertices, this); 61 | this.leafVerticesCount = __bind(this.leafVerticesCount, this); 62 | this.getLeafVertices = __bind(this.getLeafVertices, this); 63 | this.toJSON = __bind(this.toJSON, this); 64 | this.toObject = __bind(this.toObject, this); 65 | this.stringify = __bind(this.stringify, this); 66 | this.fromObject = __bind(this.fromObject, this); 67 | this.fromJSON = __bind(this.fromJSON, this); 68 | 69 | // DirectedGraph container private runtime state. 70 | this._private = { 71 | name: "", 72 | description: "", 73 | vertexMap: {}, 74 | rootMap: {}, 75 | leafMap: {}, 76 | edgeCount: 0, 77 | constructionError: null 78 | }; 79 | if ((jsonOrObject_ !== null) && jsonOrObject_) { 80 | var innerResponse = digraphImport(this, jsonOrObject_); 81 | if (innerResponse.error) { 82 | this._private.constructionError = "DirectedGraph constructor failed: " + innerResponse.error; 83 | } 84 | } 85 | } 86 | 87 | // META METHODS 88 | 89 | DirectedGraph.prototype.getGraphName = function() { 90 | return this._private.name; 91 | }; 92 | 93 | DirectedGraph.prototype.setGraphName = function(string_) { 94 | var response = { error: null, result: null }; 95 | if (helperFunctions.JSType(string_) === '[object String]') { 96 | this._private.name = string_; 97 | response.result = true; 98 | } else { 99 | response.error = "Invalid graph name specified. Expected '[object String]'."; 100 | } 101 | return response; 102 | }; 103 | 104 | DirectedGraph.prototype.getGraphDescription = function() { 105 | return this._private.description; 106 | }; 107 | 108 | DirectedGraph.prototype.setGraphDescription = function(string_) { 109 | var response = { error: null, result: null }; 110 | if (helperFunctions.JSType(string_) === '[object String]') { 111 | this._private.description = string_; 112 | response.result = true; 113 | } else { 114 | response.error = "Invalid graph name specified. Expected '[object String]'."; 115 | } 116 | return response; 117 | }; 118 | 119 | // VERTEX-SCOPE METHODS 120 | 121 | DirectedGraph.prototype.isVertex = function (vertexId_) { 122 | var innerResponse = digraphParams.verifyVertexReadRequest(vertexId_); 123 | if (innerResponse.error) { 124 | return false; 125 | } 126 | var vertex = this._private.vertexMap[vertexId_]; 127 | return (vertex !== null) && vertex && true || false; 128 | }; 129 | 130 | /* 131 | request = { 132 | u: vertex ID string 133 | p: optional property (must be serializable to JSON) 134 | } 135 | response = { 136 | error: null or error string 137 | result: vertex ID string or null if error 138 | } 139 | */ 140 | DirectedGraph.prototype.addVertex = function (request_) { 141 | var response = { error: null, result: null }; 142 | var errors = []; 143 | var inBreakScope = false; 144 | while (!inBreakScope) { 145 | inBreakScope = true; 146 | var innerResponse = digraphParams.verifyVertexWriteRequest(request_); 147 | if (innerResponse.error) { 148 | errors.unshift(innerResponse.error); 149 | break; 150 | } 151 | var vertex = this._private.vertexMap[request_.u]; 152 | if ((vertex === null) || !vertex) { 153 | vertex = this._private.vertexMap[request_.u] = {}; 154 | vertex.edges = {}; 155 | vertex.edges.in = {}; 156 | vertex.edges.out = {}; 157 | this._private.rootMap[request_.u] = {}; 158 | this._private.leafMap[request_.u] = {}; 159 | } 160 | if (helperFunctions.JSType(request_.p) !== '[object Undefined]') { 161 | vertex.properties = request_.p; 162 | } 163 | response.result = request_.u; 164 | } // end while !inBreakScope 165 | if (errors.length) { 166 | errors.unshift("DirectedGraph.addVertex failed:"); 167 | response.error = errors.join(' '); 168 | } 169 | return response; 170 | }; 171 | 172 | DirectedGraph.prototype.removeVertex = function (vertexId_) { 173 | var innerResponse = digraphParams.verifyVertexReadRequest(vertexId_); 174 | if (innerResponse.error) { 175 | return false; 176 | } 177 | var vertexU = this._private.vertexMap[vertexId_]; 178 | if ((vertexU === null) || !vertexU) { 179 | return false; 180 | } 181 | var vertexIdX; 182 | for (vertexIdX in vertexU.edges.out) { 183 | this.removeEdge({ u: vertexId_, v: vertexIdX}); 184 | } 185 | for (vertexIdX in vertexU.edges.in) { 186 | this.removeEdge({ u: vertexIdX, v: vertexId_}); 187 | } 188 | delete this._private.vertexMap[vertexId_]; 189 | delete this._private.rootMap[vertexId_]; 190 | delete this._private.leafMap[vertexId_]; 191 | return true; 192 | }; 193 | 194 | DirectedGraph.prototype.getVertexProperty = function(vertexId_) { 195 | if (!this.isVertex(vertexId_)) { 196 | return void 0; 197 | } 198 | return this._private.vertexMap[vertexId_].properties; 199 | }; 200 | 201 | /* 202 | request = { 203 | u: vertex ID string 204 | p: optional property (must be serializable to JSON) 205 | } 206 | response = { 207 | error: null or error string 208 | result: vertex ID string or null if error 209 | } 210 | */ 211 | DirectedGraph.prototype.setVertexProperty = function(request_) { 212 | return this.addVertex(request_); 213 | }; 214 | 215 | DirectedGraph.prototype.hasVertexProperty = function(vertexId_) { 216 | if (!this.isVertex(vertexId_)) { 217 | return false; 218 | } 219 | if (helperFunctions.JSType(this._private.vertexMap[vertexId_].properties) === '[object Undefined]') { 220 | return false; 221 | } 222 | return true; 223 | }; 224 | 225 | DirectedGraph.prototype.clearVertexProperty = function(vertexId_) { 226 | if (!this.isVertex(vertexId_)) { 227 | return false; 228 | } 229 | delete this._private.vertexMap[vertexId_].properties; 230 | return true; 231 | }; 232 | 233 | DirectedGraph.prototype.inDegree = function (vertexId_) { 234 | return this.isVertex(vertexId_)?Object.keys(this._private.vertexMap[vertexId_].edges.in).length:-1; 235 | }; 236 | 237 | DirectedGraph.prototype.inEdges = function(vertexId_) { 238 | var result = []; 239 | if (this.isVertex(vertexId_)) { 240 | for (var vertexIdV in this._private.vertexMap[vertexId_].edges.in) { 241 | result.push({ u: vertexIdV, v: vertexId_}); 242 | } 243 | } 244 | return result; 245 | }; 246 | 247 | DirectedGraph.prototype.outDegree = function (vertexId_) { 248 | return this.isVertex(vertexId_)?Object.keys(this._private.vertexMap[vertexId_].edges.out).length:-1; 249 | }; 250 | 251 | DirectedGraph.prototype.outEdges = function(vertexId_) { 252 | var result = []; 253 | if (this.isVertex(vertexId_)) { 254 | for (var vertexIdV in this._private.vertexMap[vertexId_].edges.out) { 255 | result.push({ u: vertexId_, v: vertexIdV}); 256 | } 257 | } 258 | return result; 259 | }; 260 | 261 | // EDGE-SCOPE METHODS 262 | 263 | /* 264 | request = { 265 | u: string, 266 | v: string, 267 | } 268 | response = Boolean true if edge exists. Otherwise, false. 269 | Note that invalid requests are coalesced as negative responses. 270 | */ 271 | DirectedGraph.prototype.isEdge = function(request_) { 272 | var response = false; 273 | var inBreakScope = false; 274 | while (!inBreakScope) { 275 | inBreakScope = true; 276 | if (digraphParams.verifyEdgeReadRequest(request_).error) { 277 | break; 278 | } 279 | var vertexU = this._private.vertexMap[request_.u]; 280 | var vertexV = this._private.vertexMap[request_.v]; 281 | if (!((vertexU !== null) && vertexU && (vertexV !== null) && vertexV)) { 282 | break; 283 | } 284 | var edge = vertexU.edges.out[request_.v]; 285 | response = (edge !== null) && edge && true || false; 286 | } 287 | return response; 288 | }; 289 | 290 | /* 291 | request = { 292 | e: { u: string, v: string }, 293 | p: (optional) property serializable to JSON 294 | } 295 | response = { 296 | error: error string or null 297 | result: edge descriptor object or null iff error 298 | } 299 | */ 300 | DirectedGraph.prototype.addEdge = function (request_) { 301 | var response = { error: null, result: null }; 302 | var errors = []; 303 | var inBreakScope = false; 304 | while (!inBreakScope) { 305 | inBreakScope = true; 306 | var innerResponse = digraphParams.verifyEdgeWriteRequest(request_); 307 | if (innerResponse.error) { 308 | errors.unshift(innerResponse.error); 309 | break; 310 | } 311 | innerResponse = this.addVertex({ u: request_.e.u }); 312 | if (innerResponse.error) { 313 | errors.unshift(innerResponse.error); 314 | break; 315 | } 316 | innerResponse = this.addVertex({ u: request_.e.v }); 317 | if (innerResponse.error) { 318 | errors.unshift(innerResponse.error); 319 | break; 320 | } 321 | var outEdge = this._private.vertexMap[request_.e.u].edges.out[request_.e.v]; 322 | if ((outEdge === null) || !outEdge) { 323 | outEdge = this._private.vertexMap[request_.e.u].edges.out[request_.e.v] = {}; 324 | delete this._private.leafMap[request_.e.u]; 325 | } 326 | var inEdge = this._private.vertexMap[request_.e.v].edges.in[request_.e.u]; 327 | if ((inEdge === null) || !inEdge) { 328 | inEdge = this._private.vertexMap[request_.e.v].edges.in[request_.e.u] = {}; 329 | this._private.edgeCount++; 330 | delete this._private.rootMap[request_.e.v]; 331 | } 332 | if (helperFunctions.JSType(request_.p) !== '[object Undefined]') { 333 | outEdge.properties = request_.p; 334 | } 335 | response.result = request_.e; 336 | } // end while !inBreakScope 337 | if (errors.length) { 338 | errors.unshift("DirectedGraph.addEdge failed:"); 339 | response.error = errors.join(' '); 340 | } 341 | return response; 342 | }; 343 | 344 | /* 345 | request = { 346 | u: string, 347 | v: string, 348 | } 349 | response = { 350 | error: null or error string explaining why result is null 351 | result: Boolean true if successful. False if edge doesn't exist. 352 | } 353 | */ 354 | DirectedGraph.prototype.removeEdge = function(request_) { 355 | var response = { error: null, result: null }; 356 | var errors = []; 357 | var inBreakScope = false; 358 | while (!inBreakScope) { 359 | inBreakScope = true; 360 | var innerResponse = digraphParams.verifyEdgeReadRequest(request_); 361 | if (innerResponse.error) { 362 | errors.unshift(innerResponse.error); 363 | break; 364 | } 365 | var vertexU = this._private.vertexMap[request_.u]; 366 | var vertexV = this._private.vertexMap[request_.v]; 367 | if (!((vertexU !== null) && vertexU && (vertexV !== null) && vertexV)) { 368 | response.result = false; 369 | break; 370 | } 371 | var outEdgeMap = vertexU.edges.out; 372 | var edge = outEdgeMap[request_.v]; 373 | if (!((edge !== null) && edge)) { 374 | response.result = false; 375 | break; 376 | } 377 | delete outEdgeMap[request_.v]; 378 | if (!Object.keys(outEdgeMap).length) { 379 | this._private.leafMap[request_.u] = {}; 380 | } 381 | var inEdgeMap = vertexV.edges.in; 382 | delete inEdgeMap[request_.u]; 383 | if (!Object.keys(inEdgeMap).length) { 384 | this._private.rootMap[request_.v] = {}; 385 | } 386 | if (this._private.edgeCount) { 387 | this._private.edgeCount--; 388 | } 389 | response.result = true; 390 | } // while !inBreakScope 391 | if (errors.length) { 392 | errors.unshift("DirectedGraph.removeEdge failed:"); 393 | response.error = errors.join(' '); 394 | } 395 | return response; 396 | }; 397 | 398 | /* 399 | request = { 400 | u: string, 401 | v: string 402 | } 403 | response = void 0 or whatever property is assigned to the edge 404 | Note that build requests are coalesced to void 0 responses. 405 | */ 406 | 407 | DirectedGraph.prototype.getEdgeProperty = function(request_) { 408 | var response = void 0; 409 | var inBreakScope = false; 410 | while (!inBreakScope) { 411 | inBreakScope = true; 412 | if (digraphParams.verifyEdgeReadRequest(request_).error) { 413 | break; 414 | } 415 | var vertexU = this._private.vertexMap[request_.u]; 416 | var vertexV = this._private.vertexMap[request_.v]; 417 | if (!((vertexU !== null) && vertexU && (vertexV !== null) && vertexV)) { 418 | break; 419 | } 420 | response = vertexU.edges.out[request_.v].properties; 421 | } 422 | return response; 423 | }; 424 | 425 | /* 426 | request = { 427 | e: { u: string, v: string }, 428 | p: (optional) property serializable to JSON 429 | } 430 | response = { 431 | error: error string or null 432 | result: edge descriptor object or null iff error 433 | } 434 | */ 435 | DirectedGraph.prototype.setEdgeProperty = function(request_) { 436 | return this.addEdge(request_); 437 | }; 438 | 439 | DirectedGraph.prototype.hasEdgeProperty = function(request_) { 440 | if (!this.isEdge(request_)) { 441 | return false; 442 | } 443 | if (helperFunctions.JSType(this._private.vertexMap[request_.u].edges.out[request_.v].properties) === '[object Undefined]') { 444 | return false; 445 | } 446 | return true; 447 | }; 448 | 449 | DirectedGraph.prototype.clearEdgeProperty = function(request_) { 450 | if (!this.isEdge(request_)) { 451 | return false; 452 | } 453 | delete this._private.vertexMap[request_.u].edges.out[request_.v].properties; 454 | return true; 455 | }; 456 | 457 | // DIGRAPH-SCOPE METHODS 458 | 459 | DirectedGraph.prototype.verticesCount = function() { 460 | return Object.keys(this._private.vertexMap).length; 461 | }; 462 | 463 | DirectedGraph.prototype.getVertices = function() { 464 | var vertices = []; 465 | for (var vertexId in this._private.vertexMap) { 466 | vertices.push(vertexId); 467 | } 468 | return vertices; 469 | }; 470 | 471 | DirectedGraph.prototype.edgesCount = function() { 472 | return this._private.edgeCount; 473 | }; 474 | 475 | DirectedGraph.prototype.getEdges = function() { 476 | var edges = []; 477 | var vertices = this.getVertices(); 478 | var processVertexOutEdges = function(outEdges_) { 479 | outEdges_.forEach(function(outEdge_) { 480 | edges.push(outEdge_); 481 | }); 482 | }; 483 | var self = this; 484 | vertices.forEach(function(vertexId_) { 485 | processVertexOutEdges(self.outEdges(vertexId_)); 486 | }); 487 | return edges; 488 | }; 489 | 490 | DirectedGraph.prototype.rootVerticesCount = function() { 491 | return Object.keys(this._private.rootMap).length; 492 | }; 493 | 494 | DirectedGraph.prototype.getRootVertices = function() { 495 | var rootVertices = []; 496 | for (var vertexId in this._private.rootMap) { 497 | rootVertices.push(vertexId); 498 | } 499 | return rootVertices; 500 | }; 501 | 502 | DirectedGraph.prototype.leafVerticesCount = function() { 503 | return Object.keys(this._private.leafMap).length; 504 | }; 505 | 506 | DirectedGraph.prototype.getLeafVertices = function() { 507 | var leafVertices = []; 508 | for (var vertexId in this._private.leafMap) { 509 | leafVertices.push(vertexId); 510 | } 511 | return leafVertices; 512 | }; 513 | 514 | // toJSON and toObject are identical delegations to digraphExport.exportObject. 515 | DirectedGraph.prototype.toJSON = function () { 516 | return digraphExport.exportObject(this); 517 | }; 518 | DirectedGraph.prototype.toObject = function() { 519 | return digraphExport.exportObject(this); 520 | }; 521 | 522 | DirectedGraph.prototype.stringify = function(replacer_, space_) { 523 | return digraphExport.exportJSON(this, replacer_, space_); 524 | }; 525 | 526 | DirectedGraph.prototype.fromObject = function (object_) { 527 | return digraphImport(this, object_); 528 | }; 529 | 530 | DirectedGraph.prototype.fromJSON = function(json_) { 531 | return digraphImport(this, json_); 532 | }; 533 | 534 | return DirectedGraph; 535 | 536 | })(); 537 | 538 | var createDirectedGraph = function (jsonOrObject_) { 539 | var response = { error: null, result: null }; 540 | var digraph = new DirectedGraph(jsonOrObject_); 541 | if (digraph._private.constructionError) { 542 | response.error = digraph._private.constructionError; 543 | } else { 544 | response.result = digraph; 545 | } 546 | return response; 547 | }; 548 | 549 | module.exports = { 550 | /* 551 | createDirectedGraph is a wrapper around JavaScript operator new jsgraph.DirectedGraph(...) 552 | that returns an error/result response object. This is the preferred mechanism by which 553 | jsgraph-derived client code should construct DirectedGraph container object instance(s). 554 | */ 555 | createDirectedGraph: createDirectedGraph, 556 | 557 | /* 558 | DirectedGraph is constructed with JavaScript operator new but may fail during construction 559 | if an error is encountered parsing the constructor's optional JSON/data object in-paramter. 560 | After contruction, clients should check DirectedGraph.constructionError === null to ensure 561 | that construction was successful. If a construction error occurred, constructionError is the 562 | response.error string returned by DirectedGraph's data import subroutine. 563 | */ 564 | DirectedGraph: DirectedGraph 565 | 566 | }; 567 | 568 | }).call(this); 569 | -------------------------------------------------------------------------------- /test/test-digraph-container.js: -------------------------------------------------------------------------------- 1 | // digraph-test.js 2 | 3 | var assert = require('chai').assert; 4 | var uuid = require('node-uuid'); 5 | 6 | var testModule = require('./module-under-test'); 7 | 8 | var DirectedGraphContainer = testModule('arc_core_digraph'); 9 | var DirectedGraph = DirectedGraphContainer.DirectedGraph; 10 | 11 | describe("DirectedGraph container object tests", function() { 12 | 13 | describe("Object construction tests", function() { 14 | var digraph = new DirectedGraph(); 15 | 16 | it("graph should be an object", function() { 17 | assert.typeOf(digraph, 'object'); 18 | }); 19 | 20 | it ("graph should have zero vertices", function() { 21 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 0); 22 | assert.equal(digraph.verticesCount(), 0); 23 | }); 24 | 25 | it("graph should have zero edges", function() { 26 | assert.equal(digraph.edgesCount(), 0); 27 | }); 28 | 29 | it ("graph should have zero root vertices", function() { 30 | assert.equal(digraph.getRootVertices().length, 0); 31 | }); 32 | 33 | it ("graph should have zero leaf vertices", function() { 34 | assert.equal(digraph.getLeafVertices().length, 0); 35 | }); 36 | }); 37 | 38 | describe("Import/Export tests", function(){ 39 | 40 | var digraph = new DirectedGraph(); 41 | var copy = new DirectedGraph(); 42 | var vertices = ["foo", "bar", "baz"]; 43 | var json = null; 44 | 45 | before(function(){ 46 | vertices.forEach(function(u){ 47 | digraph.addVertex({ u: u, p: { k: u }}); 48 | }); 49 | vertices.forEach(function(u){ 50 | vertices.forEach(function(v){ 51 | if(u !== v) digraph.addEdge({ e: { u: u, v: v}}); 52 | }); 53 | }); 54 | }); 55 | 56 | it("graph export JSON should match expected control string", function() { 57 | var expectedJSON = '{"name":"","description":"","vlist":[{"u":"foo","p":{"k":"foo"}},{"u":"bar","p":{"k":"bar"}},{"u":"baz","p":{"k":"baz"}}],"elist":[{"e":{"u":"foo","v":"bar"}},{"e":{"u":"foo","v":"baz"}},{"e":{"u":"bar","v":"foo"}},{"e":{"u":"bar","v":"baz"}},{"e":{"u":"baz","v":"foo"}},{"e":{"u":"baz","v":"bar"}}]}'; 58 | assert.equal(digraph.stringify(), expectedJSON); 59 | }); 60 | 61 | it("graph should export structured JSON string", function(){ 62 | json = digraph.stringify(); 63 | assert.isString(json); 64 | var parsed = JSON.parse(json); 65 | assert.isObject(parsed); 66 | assert.isArray(parsed.vlist); 67 | assert.isArray(parsed.elist); 68 | }); 69 | 70 | it("graph export to object and JSON should be identical", function() { 71 | var testObjectJSON = JSON.stringify(digraph.toObject()); 72 | var testJSON = digraph.stringify(); 73 | assert.equal(testObjectJSON, testJSON); 74 | }); 75 | 76 | it("graph constructed from export object should be identical to original", function() { 77 | var testGraph = new DirectedGraph(digraph.toObject()); 78 | assert.deepEqual(testGraph.toJSON(), digraph.toJSON()); 79 | }); 80 | 81 | it("empty graph filled using fromObject should be identical to original", function() { 82 | var testGraph = new DirectedGraph(); 83 | testGraph.fromObject(digraph.toObject()); 84 | assert.deepEqual(testGraph.toJSON(), digraph.toJSON()); 85 | }); 86 | 87 | it("empty graph filled using fromJSON should be identical to original", function() { 88 | var testGraph = new DirectedGraph(); 89 | testGraph.fromJSON(digraph.toJSON()); 90 | assert.deepEqual(testGraph.toJSON(), digraph.toJSON()); 91 | }); 92 | 93 | describe("Re-create the directed graph container from the JSON.", function() { 94 | var copy = null; 95 | before(function() { 96 | var constructCopy = function() { 97 | copy = new DirectedGraph(json); 98 | }; 99 | assert.doesNotThrow(constructCopy); 100 | }); 101 | it("graph should re-create identical graph from import", function(){ 102 | assert.deepEqual(copy.stringify(), json); 103 | }); 104 | }); 105 | 106 | describe("Test JSON export algorith's vertex skip facility", function() { 107 | 108 | var testGraph = null; 109 | var expectedJSON = '{"name":"test","description":"test","vlist":[{"u":"strawberry","p":"has a property"},{"u":"not-mentioned-no-property"}],"elist":[{"e":{"u":"apple","v":"orange"}},{"e":{"u":"bannana","v":"apple"}},{"e":{"u":"pineapple","v":"orange"}},{"e":{"u":"strawberry","v":"blueberry"},"p":"link"}]}'; 110 | var actualJSON = null; 111 | 112 | before(function() { 113 | var digraph = new DirectedGraph({ 114 | name: 'test', 115 | description: 'test', 116 | vlist: [ 117 | { u: 'apple' }, 118 | { u: 'orange' }, 119 | { u: 'bannana' }, 120 | { u: 'pineapple' }, 121 | { u: 'strawberry', p: "has a property" }, 122 | { u: 'blueberry' }, 123 | { u: 'not-mentioned-no-property' } 124 | ], 125 | elist: [ 126 | { e: { u: 'strawberry', v: 'blueberry' }, p: 'link' }, 127 | { e: { u: 'bannana', v: 'apple' }}, 128 | { e: { u: 'apple', v: 'orange' }}, 129 | { e: { u: 'pineapple', v: 'orange' }} 130 | ] 131 | }); 132 | actualJSON = digraph.stringify(); 133 | }); 134 | 135 | it("Exported JSON should match expected JSON value.", function() { 136 | assert.equal(actualJSON, expectedJSON); 137 | }); 138 | 139 | }); 140 | 141 | }); 142 | 143 | describe("Vertex API tests", function() { 144 | 145 | var digraph = new DirectedGraph(); 146 | 147 | var addVertexTest = function() { 148 | 149 | it("graph should have a single vertex", function() { 150 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 1); 151 | assert.equal(digraph.verticesCount(), 1); 152 | }); 153 | 154 | it("graph should have zero edges", function() { 155 | assert.equal(digraph.edgesCount(), 0); 156 | }); 157 | 158 | it ("graph should have one root vertex", function() { 159 | assert.equal(digraph.getRootVertices().length, 1); 160 | }); 161 | 162 | it ("graph should have one leaf vertex", function() { 163 | assert.equal(digraph.getLeafVertices().length, 1); 164 | }); 165 | 166 | it ("vertex should have zero in-degree", function() { 167 | assert.equal(digraph.inDegree("apple"), 0); 168 | }); 169 | 170 | it ("vertex should have zero out-degree", function() { 171 | assert.equal(digraph.outDegree("apple"), 0); 172 | }); 173 | 174 | it("vertex in-edge array should have length zero", function() { 175 | assert.lengthOf(digraph.inEdges("apple"), 0); 176 | }); 177 | 178 | it ("vertex out-edge array should have length zero", function() { 179 | assert.lengthOf(digraph.outEdges("apple"), 0); 180 | }); 181 | }; 182 | 183 | var removeVertexTest = function() { 184 | 185 | it("graph should have zero vertices", function() { 186 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 0); 187 | assert.equal(digraph.verticesCount(), 0); 188 | }); 189 | 190 | it("graph should have zero edges", function() { 191 | assert.equal(digraph.edgesCount(), 0); 192 | }); 193 | 194 | it("graph should have zero root vertices", function() { 195 | assert.equal(digraph.getRootVertices().length, 0); 196 | }); 197 | 198 | it("graph should have zero leaf vertices", function() { 199 | assert.equal(digraph.getLeafVertices().length, 0); 200 | }); 201 | }; 202 | 203 | describe("addVertex", function() { 204 | before(function() { digraph.addVertex({ u: "apple"}); }); 205 | addVertexTest(); 206 | }); 207 | describe("addVertex (idempotency)", function() { 208 | before(function() { digraph.addVertex({ u: "apple"}); }); 209 | addVertexTest(); 210 | }); 211 | describe("removeVertex", function() { 212 | before(function() { digraph.removeVertex("apple"); }); 213 | removeVertexTest(); 214 | }); 215 | describe("removeVertex (idempotency)", function() { 216 | before(function() { digraph.removeVertex("apple"); }); 217 | removeVertexTest(); 218 | }); 219 | }); 220 | 221 | describe("Edge API tests", function() { 222 | 223 | var digraph = new DirectedGraph(); 224 | 225 | var addEdgeTest = function() { 226 | 227 | it("graph should have two vertices", function() { 228 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 2); 229 | assert.equal(digraph.verticesCount(), 2); 230 | }); 231 | 232 | it("graph should have one edge", function() { 233 | assert.equal(digraph.edgesCount(), 1); 234 | }); 235 | 236 | it ("graph should have one root vertex", function() { 237 | assert.equal(digraph.getRootVertices().length, 1); 238 | }); 239 | 240 | it ("graph should have one leaf vertex", function() { 241 | assert.equal(digraph.getLeafVertices().length, 1); 242 | }); 243 | 244 | it ("vertex 'apple' should have in-degree zero", function() { 245 | assert.equal(digraph.inDegree("apple"), 0); 246 | }); 247 | 248 | it ("vertex 'apple' should have out-degree one", function() { 249 | assert.equal(digraph.outDegree("apple"), 1); 250 | }); 251 | 252 | it ("vertex 'orange' should have in-degree one", function() { 253 | assert.equal(digraph.inDegree("orange"), 1); 254 | }); 255 | 256 | it ("vertex 'orange' should have out-degree zero", function() { 257 | assert.equal(digraph.outDegree("orange"), 0); 258 | }); 259 | 260 | it("vertex 'apple' in-edge array should have length zero", function() { 261 | assert.lengthOf(digraph.inEdges("apple"), 0); 262 | }); 263 | 264 | it ("vertex 'apple' out-edge array should have length one", function() { 265 | assert.lengthOf(digraph.outEdges("apple"), 1); 266 | }); 267 | 268 | it("vertex 'orange' in-edge array should have length one", function() { 269 | assert.lengthOf(digraph.inEdges("orange"), 1); 270 | }); 271 | 272 | it ("vertex 'orange' out-edge array should have length zero", function() { 273 | assert.lengthOf(digraph.outEdges("orange"), 0); 274 | }); 275 | }; 276 | 277 | var removeEdgeTest = function() { 278 | it("graph should have two vertices", function() { 279 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 2); 280 | assert.equal(digraph.verticesCount(), 2); 281 | }); 282 | 283 | it("graph should have zero edges", function() { 284 | assert.equal(digraph.edgesCount(), 0); 285 | }); 286 | 287 | it ("graph should have two root vertices", function() { 288 | assert.equal(digraph.getRootVertices().length, 2); 289 | }); 290 | 291 | it ("graph should have two leaf vertices", function() { 292 | assert.equal(digraph.getLeafVertices().length, 2); 293 | }); 294 | 295 | it ("vertex 'apple' should have in-degree zero", function() { 296 | assert.equal(digraph.inDegree("apple"), 0); 297 | }); 298 | 299 | it ("vertex 'apple' should have out-degree zero", function() { 300 | assert.equal(digraph.outDegree("apple"), 0); 301 | }); 302 | 303 | it ("vertex 'orange' should have out-degree zero", function() { 304 | assert.equal(digraph.outDegree("orange"), 0); 305 | }); 306 | 307 | it ("vertex 'orange' should have in-degree zero", function() { 308 | assert.equal(digraph.inDegree("orange"), 0); 309 | }); 310 | 311 | it("vertex 'apple' in-edge array should have length zero", function() { 312 | assert.lengthOf(digraph.inEdges("apple"), 0); 313 | }); 314 | 315 | it ("vertex 'apple' out-edge array should have length zero", function() { 316 | assert.lengthOf(digraph.outEdges("apple"), 0); 317 | }); 318 | 319 | it("vertex 'orange' in-edge array should have length zero", function() { 320 | assert.lengthOf(digraph.inEdges("orange"), 0); 321 | }); 322 | 323 | it ("vertex 'orange' out-edge array should have length zero", function() { 324 | assert.lengthOf(digraph.outEdges("orange"), 0); 325 | }); 326 | }; 327 | 328 | describe("addEdge(apple,orange)", function() { 329 | before(function() { 330 | digraph.addEdge({ e: { u: "apple", v: "orange"}}); 331 | }); 332 | addEdgeTest(); 333 | }); 334 | 335 | describe("addEdge(apple,orange) (idempotency)", function() { 336 | before(function() { 337 | digraph.addEdge({ e: { u: "apple", v: "orange"}}); 338 | }); 339 | addEdgeTest(); 340 | }); 341 | 342 | describe("removeEdge(apple,orange)", function() { 343 | before(function() { 344 | digraph.removeEdge({u: "apple", v: "orange"}); 345 | }); 346 | removeEdgeTest(); 347 | }); 348 | 349 | describe("removeEdge(apple,orange) idempotency", function() { 350 | before(function() { 351 | digraph.removeEdge({u:"apple",v:"orange"}); 352 | }); 353 | removeEdgeTest(); 354 | }); 355 | 356 | describe("addEdge(apple,orange), removeVertex(orange)", function() { 357 | before(function() { 358 | digraph.addEdge({e:{u:"apple",v:"orange"}}); 359 | digraph.removeVertex("orange"); 360 | }); 361 | 362 | it("graph should have one vertex", function() { 363 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 1); 364 | assert.equal(digraph.verticesCount(), 1); 365 | }); 366 | 367 | it("graph should have zero edges", function() { 368 | assert.equal(digraph.edgesCount(), 0); 369 | }); 370 | 371 | it ("graph should have one root vertex", function() { 372 | assert.equal(digraph.getRootVertices().length, 1); 373 | }); 374 | 375 | it ("graph should have one leaf vertex", function() { 376 | assert.equal(digraph.getLeafVertices().length, 1); 377 | }); 378 | 379 | it ("vertex 'apple' should have in-degree zero", function() { 380 | assert.equal(digraph.inDegree("apple"), 0); 381 | }); 382 | 383 | it ("vertex 'apple' should have out-degree zero", function() { 384 | assert.equal(digraph.outDegree("apple"), 0); 385 | }); 386 | 387 | it("vertex in-edge array should have length zero", function() { 388 | assert.lengthOf(digraph.inEdges("apple"), 0); 389 | }); 390 | 391 | it ("vertex out-edge array should have length zero", function() { 392 | assert.lengthOf(digraph.outEdges("apple"), 0); 393 | }); 394 | 395 | }); 396 | }); 397 | 398 | describe("ring topology tests", function() { 399 | 400 | // create a triangle. 401 | var digraph = new DirectedGraph(); 402 | digraph.addEdge({e: { u: "white", v: "blue"}}); 403 | digraph.addEdge({e: { u: "blue", v: "green"}}); 404 | digraph.addEdge({e:{ u: "green", v: "white"}}); 405 | 406 | it("graph should have three vertices", function() { 407 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 3); 408 | assert.equal(digraph.verticesCount(), 3); 409 | }); 410 | 411 | it("graph should have three edges", function() { 412 | assert.equal(digraph.edgesCount(), 3); 413 | }); 414 | 415 | it ("graph should have zero root vertices", function() { 416 | assert.equal(digraph.getRootVertices().length, 0); 417 | }); 418 | 419 | it ("graph should have zero leaf vertices", function() { 420 | assert.equal(digraph.getLeafVertices().length, 0); 421 | }); 422 | 423 | it ("vertex 'blue' should have in-degree one", function() { 424 | assert.equal(digraph.inDegree("blue"), 1); 425 | }); 426 | 427 | it ("vertex 'blue' should have out-degree one", function() { 428 | assert.equal(digraph.outDegree("blue"), 1); 429 | }); 430 | 431 | it("vertex 'blue' in-edge array should have length one", function() { 432 | assert.lengthOf(digraph.inEdges("blue"), 1); 433 | }); 434 | 435 | it ("vertex 'blue' out-edge array should have length one", function() { 436 | assert.lengthOf(digraph.outEdges("blue"), 1); 437 | }); 438 | }); 439 | 440 | describe("cube topology tests", function() { 441 | 442 | var digraph = new DirectedGraph(); 443 | 444 | describe("create cube topology tests", function() { 445 | 446 | before(function() { 447 | 448 | digraph.addEdge({e:{ u: "north1", v: "east1"}}); 449 | digraph.addEdge({e:{ u: "east1", v: "south1"}}); 450 | digraph.addEdge({ e: { u: "south1", v: "west1"}}); 451 | digraph.addEdge({ e: { u: "west1", v: "north1"}}); 452 | 453 | digraph.addEdge({ e: { u: "north2", v: "east2"}}); 454 | digraph.addEdge({ e: { u: "east2", v: "south2"}}); 455 | digraph.addEdge({ e: { u: "south2", v: "west2"}}); 456 | digraph.addEdge({ e: { u: "west2", v: "north2"}}); 457 | 458 | digraph.addEdge({ e: { u: "north1", v: "north2"}}); 459 | digraph.addEdge({ e: { u: "east1", v: "east2"}}); 460 | digraph.addEdge({ e: { u: "south1", v: "south2"}}); 461 | digraph.addEdge({ e: { u: "west1", v: "west2"}}); 462 | 463 | }); 464 | 465 | it("graph should have eigth vertices", function() { 466 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 8); 467 | assert.equal(digraph.verticesCount(), 8); 468 | }); 469 | 470 | it("graph should have twelve edges", function() { 471 | assert.equal(digraph.edgesCount(), 12); 472 | }); 473 | 474 | it ("graph should have zero root vertices", function() { 475 | assert.equal(digraph.getRootVertices().length, 0); 476 | }); 477 | 478 | it ("graph should have zero leaf vertices", function() { 479 | assert.equal(digraph.getLeafVertices().length, 0); 480 | }); 481 | 482 | it ("vertex 'north1' should have in-degree one", function() { 483 | assert.equal(digraph.inDegree("north1"), 1); 484 | }); 485 | 486 | it ("vertex 'north1' should have out-degree two", function() { 487 | assert.equal(digraph.outDegree("north1"), 2); 488 | }); 489 | 490 | it("vertex 'north1' in-edge array should have length one", function() { 491 | assert.lengthOf(digraph.inEdges("north1"), 1); 492 | }); 493 | 494 | it ("vertex 'north1' out-edge array should have length two", function() { 495 | assert.lengthOf(digraph.outEdges("north1"), 2); 496 | }); 497 | }); 498 | 499 | describe("remove cube corner tests", function() { 500 | 501 | before(function() { 502 | digraph.removeVertex("south2"); 503 | }); 504 | 505 | it("graph should have seven vertices", function() { 506 | assert.lengthOf(Object.keys(digraph._private.vertexMap), 7); 507 | assert.equal(digraph.verticesCount(), 7); 508 | }); 509 | 510 | it("graph should have nine edges", function() { 511 | assert.equal(digraph.edgesCount(), 9); 512 | }); 513 | 514 | it ("graph should have zero root vertices", function() { 515 | assert.equal(digraph.getRootVertices().length, 0); 516 | }); 517 | 518 | it ("graph should have one leaf vertices", function() { 519 | assert.equal(digraph.getLeafVertices().length, 1); 520 | }); 521 | 522 | it ("vertex 'east2' should have in-degree two", function() { 523 | assert.equal(digraph.inDegree("east2"), 2); 524 | }); 525 | 526 | it ("vertex 'east2' should have out-degree zero", function() { 527 | assert.equal(digraph.outDegree("east2"), 0); 528 | }); 529 | 530 | it("vertex 'east2' in-edge array should have length two", function() { 531 | assert.lengthOf(digraph.inEdges("east2"), 2); 532 | }); 533 | 534 | it ("vertex 'east2' out-edge array should have length zero", function() { 535 | assert.lengthOf(digraph.outEdges("east2"), 0); 536 | }); 537 | }); 538 | }); 539 | 540 | describe("Property get/set/has/clear tests", function() { 541 | 542 | var digraph = new DirectedGraph(); 543 | 544 | it("'hasVertexProperty' on non-existent vertex should return false", function() { 545 | assert.isFalse(digraph.hasVertexProperty("does not exist")); 546 | assert.isUndefined(digraph.getVertexProperty("does not exist")); 547 | }); 548 | 549 | it("'hasVertexProperty' on vertex added w/out property should return false", function() { 550 | digraph.addVertex({ u: 'test' }); 551 | assert.isFalse(digraph.hasVertexProperty("test")); 552 | assert.isUndefined(digraph.getVertexProperty("test")); 553 | }); 554 | 555 | it("'hasVertexProperty' on vertex added w/property should return true", function() { 556 | digraph.addVertex({ u: 'test1', p: "some data" }); 557 | assert.isTrue(digraph.hasVertexProperty("test1")); 558 | assert.equal(digraph.getVertexProperty("test1"), "some data"); 559 | }); 560 | 561 | it("'clearVertexProperty' on non-existent vertex should return false", function() { 562 | assert.isFalse(digraph.clearVertexProperty("does not exist")); 563 | }); 564 | 565 | it("'clearVertexProperty' on vertex that exists should return true (regardless of if it has a property)", function() { 566 | assert.isTrue(digraph.clearVertexProperty("test")); 567 | }); 568 | 569 | it("'hasVertexProperty' should return false on vertex w/property after it is cleared", function() { 570 | assert.isTrue(digraph.clearVertexProperty("test1")); 571 | assert.isFalse(digraph.hasVertexProperty("test1")); 572 | }); 573 | 574 | it("'hasEdgeProperty' should return false on non-existent edge", function() { 575 | assert.isFalse(digraph.hasEdgeProperty({ u: "test", v: "test1" })); 576 | }); 577 | 578 | it("'hasEdgeProperty' should return false on edge added w/no property", function() { 579 | digraph.addEdge({e:{u:'test', v:'test1'}}); 580 | assert.isFalse(digraph.hasEdgeProperty({u:'test',v:'test1'})); 581 | }); 582 | 583 | it("'hasEdgeProperty' should return true on edge added w/property", function() { 584 | digraph.addEdge({e:{u:'apple',v:'orange'},p:'fruit'}); 585 | assert.isTrue(digraph.hasEdgeProperty({u:'apple', v:'orange'})); 586 | }); 587 | 588 | it("'clearEdgeProperty' should return false on non-existent edge", function() { 589 | assert.isFalse(digraph.clearEdgeProperty({u:'bull',v:'shit'})); 590 | }); 591 | 592 | it("'clearEdgeProperty' should return true on edge that doesn't have a property", function() { 593 | assert.isTrue(digraph.clearEdgeProperty({u:'test',v:'test1'})); 594 | }); 595 | 596 | it("'clearEdgeProperty' should return true on edge has a property", function() { 597 | assert.isTrue(digraph.clearEdgeProperty({u:'apple',v:'orange'})); 598 | }); 599 | 600 | }); 601 | 602 | describe("Graph stress", function() { 603 | 604 | var verticesToAllocate = 1000000; 605 | var digraph = new DirectedGraph(); 606 | var x; 607 | for (x = 0; x < verticesToAllocate; x++) { 608 | digraph.addVertex({ u: "" + x + ""}); 609 | } 610 | 611 | it("graph should have " + verticesToAllocate + " vertices", function() { 612 | assert.lengthOf(Object.keys(digraph._private.vertexMap), verticesToAllocate); 613 | assert.equal(digraph.verticesCount(), verticesToAllocate); 614 | }); 615 | }); 616 | }); 617 | 618 | 619 | 620 | --------------------------------------------------------------------------------