HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Breadth first search implementation (also includes functions for finding shortest path and number of connected components)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
includesfunctionspathnumbersearchshortestbreadthalsocomponentsfirst

Problem

I've been practicing my algorithms using The Algorithm Design Manual. I've decided to implement the breadth first search section (5.7) using javascript. I also have a findShortestPath and a connectedComponents function that utilize my BFS. My BFS function accepts an adjacency list.

```
/ A Queue object for queue-like functionality over JavaScript arrays. /
var Queue = function() {
this.items = [];
};
Queue.prototype.enqueue = function(obj) {
this.items.push(obj);
};
Queue.prototype.dequeue = function() {
return this.items.shift();
};
Queue.prototype.isEmpty = function() {
return this.items.length === 0;
};

/*
* Performs a breadth-first search on a graph
* @param {array} graph - Graph, represented as adjacency lists.
* @param {number} source - The index of the source vertex.
* @returns {array} Array of objects describing each vertex, like
* [{distance: _, predecessor: _ }]
*/
var doBFS = function(graph, source, processVertexEarly, processVertexLate, processEdge, logOff) {
if (typeof logOff === 'undefined') logOff = false;
if (!logOff) console.log();
if (!logOff) console.log('*****');
if (!logOff) console.log('bfs starting at', source, 'for adjacency list:');
if (!logOff) console.log(graph);
var bfsInfo = [];

for (var i = 0; i < graph.length; i++) {
bfsInfo[i] = {
distance: null,
predecessor: null,
processed: false,
discovered: false
};
}

bfsInfo[source].distance = 0;

var queue = new Queue();
queue.enqueue(source);

// Traverse the graph

// As long as the queue is not empty:
// Repeatedly dequeue a vertex u from the queue.
//
// For each neighbor v of u that has not been visited:
// Set distance to 1 greater than u's distance
// Set predecessor to u
// Enqueue v
//
var u, v, adjList;
while (!queue.isEmpty()) {
u = queue.dequeue();

Solution

First, Queue is an unnecessary abstraction. JS arrays already provide methods to make it act like a queue. Use them directly instead.

Next is that your logic is peppered with console.log calls, especially the visually annoying ######### lines. I suggest you make a helper function that accepts a message. That helper message can then do the formatting, saving your logic from unnecessary presentation code.

I'm also wondering why you need to pass in functions. Why can't you call them straight away? Especially when PVL PVE and PE are, again, for presentation only.

I suggest you break down your operation into little functions. doBFS is quite large and at first glance, we don't really know what it's doing (other than the fact that you said its a breadth-first search, we'll take your word for that).

One neat trick so your functions don't get cluttered with logging is monkey-patching. It's a crude form of what other languages call annotations/decorators. It's just creating a function that wraps your original function along with some other functions, and calling that function instead.

function patch(patchFn, origFn){
  return function(){
    patchFn.apply(this, arguments);
    origFn.apply(this, arguments);
  }
}

function logger(){/* You get everything that was passed via arguments */}
function yourFunction(foo, bar, baz){/* your function remains clean */}

var patchedFunction = patch(logger, yourFunction);

// call patchedFunction instead.

Code Snippets

function patch(patchFn, origFn){
  return function(){
    patchFn.apply(this, arguments);
    origFn.apply(this, arguments);
  }
}

function logger(){/* You get everything that was passed via arguments */}
function yourFunction(foo, bar, baz){/* your function remains clean */}

var patchedFunction = patch(logger, yourFunction);

// call patchedFunction instead.

Context

StackExchange Code Review Q#111379, answer score: 6

Revisions (0)

No revisions yet.