From f11acff83703e01b391fcc09316ff1e304208bd4 Mon Sep 17 00:00:00 2001 From: Lucas Dohmen Date: Wed, 13 Jun 2012 11:55:26 +0200 Subject: [PATCH] Enhanced pathTo for directed and labelled graphs The `pathTo` method accepts an options hash. Currently supported: * direction: "both", "outbound", "inbound" * labels: undefined or Array of Strings --- js/common/modules/graph.js | 61 +++++++++++++------- js/common/tests/shell-graph-algorithms.js | 69 ++++++++++++++++++++++- 2 files changed, 109 insertions(+), 21 deletions(-) diff --git a/js/common/modules/graph.js b/js/common/modules/graph.js index f1297551f1..ec7f72d653 100644 --- a/js/common/modules/graph.js +++ b/js/common/modules/graph.js @@ -80,6 +80,18 @@ Array.prototype.pushIfNotFound = function (element) { } }; +//////////////////////////////////////////////////////////////////////////////// +/// @brief add all elements of the other array, that are not already in here +//////////////////////////////////////////////////////////////////////////////// + +Array.prototype.merge = function (other_array) { + var i; + + for (i = 0; i < other_array.length; i += 1) { + this.pushIfNotFound(other_array[i]); + } +}; + //////////////////////////////////////////////////////////////////////////////// /// @brief shallow copy properties //////////////////////////////////////////////////////////////////////////////// @@ -773,25 +785,23 @@ Vertex.prototype.setProperty = function (name, value) { //////////////////////////////////////////////////////////////////////////////// /// @brief find the shortest path to a certain vertex, return the ID /// -/// @FUN{@FA{vertex}.pathTo(@FA{target_vertex})} +/// @FUN{@FA{vertex}.pathTo(@FA{target_vertex}, @FA{options})} /// //////////////////////////////////////////////////////////////////////////////// -Vertex.prototype.pathTo = function (target_vertex) { - var predecessors = {}; // { ID => [ID] } - - predecessors = target_vertex.determinePredecessors(this.getId()); +Vertex.prototype.pathTo = function (target_vertex, options) { + var predecessors = target_vertex.determinePredecessors(this.getId(), options || {}); return target_vertex.pathesForTree(predecessors); }; //////////////////////////////////////////////////////////////////////////////// -/// @brief Helper function for pathTo +/// @brief determine all the pathes to this node from source id /// -/// @FUN{@FA{vertex}.determinePredecessors(@FA{source_id})} +/// @FUN{@FA{vertex}.determinePredecessors(@FA{source_id}, @FA{options})} /// //////////////////////////////////////////////////////////////////////////////// -Vertex.prototype.determinePredecessors = function (source_id) { +Vertex.prototype.determinePredecessors = function (source_id, options) { var determined_list = [], // [ID] predecessors = {}, // { ID => [ID] } todo_list = [source_id], // [ID] @@ -803,43 +813,54 @@ Vertex.prototype.determinePredecessors = function (source_id) { while (todo_list.length > 0) { current_vertex_id = this._getShortestDistance(todo_list, distances); + current_vertex = this._graph.getVertex(current_vertex_id); if (current_vertex_id === this.getId()) { break; } else { todo_list.removeLastOccurrenceOf(current_vertex_id); determined_list.push(current_vertex_id); - current_vertex = this._graph.getVertex(current_vertex_id); - current_vertex.processNeighbors( + + todo_list.merge(current_vertex._processNeighbors( determined_list, - todo_list, distances, - predecessors - ); + predecessors, + options + )); } } return predecessors; }; -Vertex.prototype.processNeighbors = function (determined_list, todo_list, distances, predecessors) { +//////////////////////////////////////////////////////////////////////////////// +/// @brief Helper function for determinePredecessors (changes distance and predecessors +/// +/// @FUN{@FA{vertex}._processNeighbors(@FA{determinedt}, @FA{distances}, @FA{predecessors})} +/// +//////////////////////////////////////////////////////////////////////////////// + +Vertex.prototype._processNeighbors = function (determined_list, distances, predecessors, options) { var i, current_neighbor_id, current_distance, raw_neighborlist, - compared_distance; + compared_distance, + not_determined_neighbors = []; - // FIXME: Choose the neighbors according to the graph (directed/undirected) - raw_neighborlist = this.getNeighbors('both'); + // FIXME: Choose the neighbors according to labels + raw_neighborlist = this.getNeighbors(options.direction || 'both', options.labels); + // console.log("Processing Neighbors for: " + this.getId()); + // console.log(raw_neighborlist); for (i = 0; i < raw_neighborlist.length; i += 1) { current_neighbor_id = raw_neighborlist[i]; if (determined_list.lastIndexOf(current_neighbor_id) === -1) { - // FIXME: Take the weight if it is a weighted graph + // FIXME: Weights other than 1 current_distance = distances[this.getId()] + 1; - todo_list.pushIfNotFound(current_neighbor_id); + not_determined_neighbors.push(current_neighbor_id); compared_distance = distances[current_neighbor_id]; if ((compared_distance === undefined) || (compared_distance > current_distance)) { @@ -850,6 +871,8 @@ Vertex.prototype.processNeighbors = function (determined_list, todo_list, distan } } } + + return not_determined_neighbors; }; //////////////////////////////////////////////////////////////////////////////// diff --git a/js/common/tests/shell-graph-algorithms.js b/js/common/tests/shell-graph-algorithms.js index 5807dda9ed..ac4ecfe2a6 100644 --- a/js/common/tests/shell-graph-algorithms.js +++ b/js/common/tests/shell-graph-algorithms.js @@ -152,7 +152,7 @@ function dijkstraSuite() { graph.addEdge(v1, v2, 8, 'a'); graph.addEdge(v1, v3, 9, 'b'); - result_array = v1.getNeighbors('both', ['a']); + result_array = v1.getNeighbors('both', ['a', 'c']); assertEqual(result_array.length, 1); assertEqual(result_array[0], 2); @@ -218,7 +218,72 @@ function dijkstraSuite() { assertEqual(path[0].toString(), v1.getId()); assertEqual(path[1].toString(), v2.getId()); assertEqual(path[2].toString(), v3.getId()); - } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a short, distinct path on a directed graph +//////////////////////////////////////////////////////////////////////////////// + + testGetADirectedDistinctPath : function () { + var v1 = graph.addVertex(1), + v2 = graph.addVertex(2), + v3 = graph.addVertex(3), + v4 = graph.addVertex(4), + e1 = graph.addEdge(v1, v2), + e2 = graph.addEdge(v2, v3), + e3 = graph.addEdge(v3, v4), + e4 = graph.addEdge(v4, v1), + pathes = v1.pathTo(v3, {direction: "outbound"}); + + assertEqual(pathes.length, 1); + assertEqual(pathes[0][0].toString(), v1.getId()); + assertEqual(pathes[0][1].toString(), v2.getId()); + assertEqual(pathes[0][2].toString(), v3.getId()); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a short, distinct path on a directed graph +//////////////////////////////////////////////////////////////////////////////// + + testGetADirectedLabeledPath : function () { + var v1 = graph.addVertex(1), + v2 = graph.addVertex(2), + v3 = graph.addVertex(3), + v4 = graph.addVertex(4), + e1 = graph.addEdge(v1, v2, 5, "no"), + e2 = graph.addEdge(v1, v3, 6, "yes"), + e3 = graph.addEdge(v3, v4, 7, "yeah"), + e4 = graph.addEdge(v4, v2, 8, "yes"), + pathes = v1.pathTo(v2, { labels: ["yes", "yeah"] }); + + assertEqual(pathes.length, 1); + assertEqual(pathes[0][0].toString(), v1.getId()); + assertEqual(pathes[0][1].toString(), v3.getId()); + assertEqual(pathes[0][2].toString(), v4.getId()); + assertEqual(pathes[0][3].toString(), v2.getId()); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a short, distinct path on a weighted graph +//////////////////////////////////////////////////////////////////////////////// + + /*testGetADirectedWeightedPath : function () { + var v1 = graph.addVertex(1), + v2 = graph.addVertex(2), + v3 = graph.addVertex(3), + v4 = graph.addVertex(4), + e1 = graph.addEdge(v1, v2, 5, "5", { my_weight: 5 }), + e2 = graph.addEdge(v1, v3, 6, "6", { my_weight: 1 }), + e3 = graph.addEdge(v3, v4, 7, "7", { my_weight: 1 }), + e4 = graph.addEdge(v4, v1, 8, "8", { my_weight: 1 }), + pathes = v1.pathTo(v2, { weight: "my_weight" }); + + assertEqual(pathes.length, 1); + assertEqual(pathes[0][0].toString(), v1.getId()); + assertEqual(pathes[0][1].toString(), v3.getId()); + assertEqual(pathes[0][2].toString(), v4.getId()); + assertEqual(pathes[0][3].toString(), v2.getId()); + }*/ }; }