Skip to content
Browse files

added ability to modify keys in dheap by asking client to take care o…

…f identity of complex objects. added basic graph implementation.
  • Loading branch information...
1 parent 3480826 commit d5f54df2042b48e87b762c4286b32febe74d0166 @DavidMcLaughlin committed
Showing with 347 additions and 3 deletions.
  1. +71 −3 dheap/dheap.js
  2. +32 −0 dheap/test.html
  3. +2 −0 graph/README
  4. +143 −0 graph/graph.js
  5. +99 −0 graph/test.html
View
74 dheap/dheap.js
@@ -13,20 +13,50 @@ var collections = collections || {};
* [1] where one element being less than the other is something
* that can be decided by the developer.
*/
- var dheap = function(d, comparator) {
+ var dheap = function(d, comparator, key_extractor) {
this.d = d;
this.elements = [-1];
+ this.key_cache = {};
this.comparator = comparator;
+ // a functiont to extract a unique key from complex objects
+ this.key_extractor = key_extractor;
};
- dheap.heapify = function(els, d, comparator) {
- var heap = new dheap(d, comparator), i;
+ dheap.heapify = function(els, d, comparator, key_extractor) {
+ var heap = new dheap(d, comparator, key_extractor), i;
for(i = 0; i < els.length; i++) {
heap.offer(els[i]);
}
return heap;
};
+ dheap.prototype._extract_key = function(el) {
+ if(this.key_extractor) {
+ return this.key_extractor(el);
+ } else {
+ return el;
+ }
+ };
+
+ dheap.create = function(options) {
+ options = options || {};
+ return new dheap(options.children || 2,
+ options.comparator,
+ options.key_extractor);
+ };
+
+ dheap.prototype._update_key = function(el, idx) {
+ this.key_cache[this._extract_key(el)] = idx;
+ };
+
+ dheap.prototype._get_idx = function(el) {
+ return this.key_cache[this._extract_key(el)];
+ };
+
+ dheap.prototype.peek = function() {
+ return this.elements[1];
+ };
+
/**
* Takes the first element from the heap. If
* no element is available, we just don't return
@@ -48,6 +78,7 @@ var collections = collections || {};
this.elements[1] = last;
}
this._bubble_down(1);
+ this._update_key(min, null);
return min;
}
};
@@ -73,6 +104,7 @@ var collections = collections || {};
dheap.prototype.offer = function(el) {
var tail = this.elements.length;
this.elements[tail] = el;
+ this._update_key(el, tail);
this._bubble_up(tail);
};
@@ -107,7 +139,9 @@ var collections = collections || {};
if(smallest_child !== n) {
tmp = this.elements[n];
this.elements[n] = this.elements[smallest_child];
+ this._update_key(this.elements[n], n);
this.elements[smallest_child] = tmp;
+ this._update_key(tmp, smallest_child);
this._bubble_down(smallest_child);
}
};
@@ -148,11 +182,45 @@ var collections = collections || {};
if(cmp < 0) {
// swap them
this.elements[n] = this.elements[parent_idx];
+ this._update_key(this.elements[n], n);
this.elements[parent_idx] = el;
+ this._update_key(el, parent_idx);
this._bubble_up(parent_idx);
}
};
+ /**
+ * Decrease the priority of a value.
+ */
+ dheap.prototype.update = function(value) {
+ var curr, curr_idx, cmp;
+
+ if(!this.key_extractor) {
+ throw "You need to define a stable key extractor to update elements.";
+ }
+
+ curr_idx = this._get_idx(value);
+ if(!curr_idx) {
+ return false;
+ }
+ curr = this.elements[curr_idx];
+ this.elements[curr_idx] = value;
+
+ if(!curr) {
+ return false;
+ }
+
+ // we might need to reposition this if the priority
+ // has increased or decreased
+ cmp = this._compare(value, curr);
+ if(cmp < 0) {
+ this._bubble_up(curr_idx);
+ } else if(cmp > 0) {
+ this._bubble_down(curr_idx);
+ }
+ return true;
+ };
+
dheap.prototype._parent = function(n) {
var pidx;
if(n <= 1) { return -1; }
View
32 dheap/test.html
@@ -74,6 +74,38 @@
deepEqual(sorted, target, "Custom sort on arrays");
});
+
+ test("simple update", function() {
+ var dheap = collections.dheap.heapify([5,4,2,1,3,6,7,9], 4);
+ equal(dheap.peek(), 1, "Test");
+ try {
+ dheap.update(200);
+ equal(true, false, "update should have thrown exception");
+ } catch(e) {
+ equal(true, true, "Update threw exception with no key extractor.");
+ }
+ });
+
+ test("key extraction for complex objects and updating priorities", function() {
+ var dheap, sorted, key_ex, cmp, success;
+
+ key_ex = function(el) { return el.key; }
+ cmp = function(a, b) { return(a.priority < b.priority) ? -1 : 1; }
+
+ dheap = new collections.dheap(4, cmp, key_ex);
+
+ dheap.offer({ key: 'One', priority: 5 });
+ dheap.offer({ key: 'Two', priority: 2 });
+ dheap.offer({ key: 'Three', priority: 3 });
+
+ equals(dheap.peek().key, 'Two', "current min");
+
+ success = dheap.update({ key: 'One', priority: 1});
+ equals(success, true, "update went successful");
+
+ equals(dheap.poll().key, 'One', "min changed when it was updated");
+ });
+
module("heapify");
test("heapify constructor works", function() {
var dheap = collections.dheap.heapify([4,2,1,5,6,10,9,3,7,8], 4),
View
2 graph/README
@@ -0,0 +1,2 @@
+Implementation of a directed graph using adjacency list with bfs, dfs and dijkstra's shortest
+path implementations.
View
143 graph/graph.js
@@ -0,0 +1,143 @@
+var collections = collections || {};
+(function(ns) {
+
+ var is_array = function(obj) {
+ if (obj.constructor.toString().indexOf("Array") === -1) {
+ return false;
+ }
+ return true;
+ };
+
+ var graph = function() {
+ this.edges = {};
+ };
+
+ graph.prototype.add_node = function(node) {
+ if(!this.edges.hasOwnProperty(node)) {
+ this.edges[node] = [];
+ }
+ };
+
+ /**
+ * Add an edge between from and to nodes. If the
+ * nodes don't already exist, they are initialised.
+ *
+ * You can also attach meta information (such as weights
+ * or labels) to the edge. If meta information is attached,
+ * the edge stores the information next to the target node
+ * in an array.
+ */
+ graph.prototype.add_edge = function(from, to, meta) {
+ var outgoing;
+ this.add_node(from);
+ outgoing = this.edges[from];
+ if(meta) {
+ outgoing[outgoing.length] = [to, meta];
+ } else {
+ outgoing[outgoing.length] = to;
+ }
+ };
+
+ graph.prototype.add_bidi_edge = function(from, to, meta) {
+ this.add_edge(from, to, meta);
+ this.add_edge(to, from, meta);
+ };
+
+ graph.prototype.get_edges = function(node) {
+ return this.edges[node] || [];
+ };
+
+ graph.prototype.dfs = function(node, target, fn) {
+ return this.search(node, target, fn, function(edges) { return edges.pop(); });
+ };
+
+ graph.prototype.bfs = function(node, target, fn) {
+ return this.search(node, target, fn, function(edges) { return edges.shift(); });
+ };
+
+ var get_node_from_edge = function(edge) {
+ if(is_array(edge)) {
+ return edge[0];
+ } else {
+ return edge;
+ }
+ };
+
+ var get_distance_from_edge = function(edge) {
+ if(is_array(edge)) {
+ return edge[1];
+ } else {
+ return 1;
+ }
+ };
+
+ graph.prototype.search = function(node, target, fn, next_fn) {
+ var visited = {}, edges = new Array(), next, e, i;
+ edges.push(node);
+
+ while(edges.length > 0) {
+ next = next_fn(edges);
+ if(visited.hasOwnProperty(next)) {
+ continue;
+ }
+ visited[next] = true;
+ fn(next);
+ if(target && next == target) {
+ break;
+ }
+ e = this.get_edges(next);
+ for(var i = 0; i < e.length; i++) {
+ edges.push(get_node_from_edge(e[i]));
+ }
+ }
+ };
+
+ var edge_cmp = function(a, b) {
+ return (get_distance_from_edge(a) < get_distance_from_edge(b)) ? -1 : 1;
+ };
+
+ var key_extractor = function(node) {
+ return get_node_from_edge(node);
+ };
+
+ /**
+ * Dijkstra's Algorithm
+ */
+ graph.prototype.shortest_path = function(node, target) {
+ var distance = {}, next, neightbours, i, previous = {},
+ path = [], next_node, n, new_dist;
+ var edges = new collections.dheap(4, edge_cmp, key_extractor);
+ edges.offer([node, 0]);
+ distance[node] = 0;
+ while(!edges.is_empty()) {
+ next = edges.poll();
+ next_node = get_node_from_edge(next);
+ if(distance[next_node] === undefined) { break; }
+
+ neighbours = this.get_edges(next_node);
+ for(i = 0; i < neighbours.length; i++) {
+ if(!is_array(neighbours[i])) {
+ neighbours[i] = [neighbours[i], 1];
+ }
+ n = get_node_from_edge(neighbours[i]);
+ new_dist = distance[next_node] + get_distance_from_edge(neighbours[i]);
+ if(distance[n] === undefined || new_dist < distance[n]) {
+ distance[n] = new_dist;
+ previous[n] = next_node;
+ if(!edges.update([n, new_dist])) {
+ edges.offer([n, new_dist]);
+ }
+ }
+ }
+ }
+ if(previous[target]) {
+ while(previous[target] !== undefined) {
+ path.unshift(target);
+ target = previous[target];
+ }
+ return path;
+ }
+ };
+
+ ns.graph = graph;
+}(collections));
View
99 graph/test.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+"http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <link rel="stylesheet" href="../test/qunit.css" type="text/css"/>
+ <script src="http://code.jquery.com/jquery-latest.js"> </script>
+ <script src="graph.js"> </script>
+ <script src="../dheap/dheap.js"></script>
+ <script type="text/javascript" src="../test/qunit.js"></script>
+ <script type="text/javascript">
+ $(function() {
+
+ var create_test_graph = function() {
+ var graph = new collections.graph();
+ /**
+ * ( 1 )
+ * / \
+ * (2) (3)
+ * \ /
+ * (5) -- (4)
+ * \ /
+ * (6)
+ */
+ graph.add_edge(1, 2);
+ graph.add_edge(1, 3);
+ graph.add_edge(2, 1);
+ graph.add_edge(2, 5);
+ graph.add_edge(3, 1);
+ graph.add_edge(3, 4);
+ graph.add_edge(4, 3);
+ graph.add_edge(4, 5);
+ graph.add_edge(4, 6);
+ graph.add_edge(5, 2);
+ graph.add_edge(5, 4);
+ graph.add_edge(5, 6);
+ graph.add_edge(6, 4);
+ graph.add_edge(6, 5);
+ return graph;
+ };
+
+
+ module("basic graph tests");
+ test("fetching edges", function() {
+ var graph = create_test_graph(), edges, count;
+
+ edges = graph.get_edges(1);
+
+ deepEqual([2, 3], edges, "edges returned ok");
+
+ count = 0;
+
+ graph.dfs(1, null, function(node) {
+ count += node;
+ });
+
+ equals(count, 21, "dfs with no target visits all six nodes only once.");
+
+ count = 0;
+
+ graph.bfs(1, null, function(node) {
+ count += node;
+ });
+
+ equals(count, 21, "bfs with no target visits all nodes only once.");
+
+ deepEqual(graph.shortest_path(3, 6), [4,6], "shortest path works.");
+ deepEqual(graph.shortest_path(5, 4), [4], "shortest path with 1 edge.");
+ deepEqual(graph.shortest_path(6, 1), [4, 3, 1], "Tie breaker...");
+ });
+
+ test("weighted graphs", function() {
+ var g = new collections.graph();
+
+ // http://en.wikipedia.org/wiki/Dijkstra's_algorithm
+ g.add_bidi_edge(1, 2, 1);
+ g.add_bidi_edge(1, 6, 14);
+ g.add_bidi_edge(1, 3, 9);
+ g.add_bidi_edge(2, 3, 10);
+ g.add_bidi_edge(2, 4, 15);
+ g.add_bidi_edge(3, 4, 11);
+ g.add_bidi_edge(3, 6, 2);
+ g.add_bidi_edge(4, 5, 6);
+ g.add_bidi_edge(5, 6, 9);
+
+ deepEqual(g.shortest_path(1, 5), [3, 6, 5], "weighted shortest path works");
+ deepEqual(g.shortest_path(5, 1), [6, 3, 1], "reverse route");
+ deepEqual(g.shortest_path(2, 6), [3, 6], "shorty");
+ });
+ });
+ </script>
+ </head>
+ <body>
+ <h1 id="qunit-header">QUnit example</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests">
+ </ol>
+ </body>
+</html>

0 comments on commit d5f54df

Please sign in to comment.
Something went wrong with that request. Please try again.