Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions 3-Solving-Problems-By-Searching/bi-directional.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//Function to clone a object
function cloneObject(obj) {
return JSON.parse(JSON.stringify(obj));
}
//Function to calculate euclidean distance
function distance(point1, point2) {
return Math.sqrt(Math.pow(point1[0] - point2[0], 2) + Math.pow(point1[1] - point2[1], 2));
}

class Node {
constructor(id, x, y, adjacent) {
this.id = id;
this.x = x;
this.y = y;
this.adjacent = (adjacent != undefined) ? adjacent : [];
}
}
//Generates a random planar graph
function randomPlanarGraph(height, width, totalNodes) {
let nodes = [],
edges = [];
let grid = gitteredGrid(height, width, totalNodes);
let rowSize = grid.length;
let columnSize = grid[0].length;
//Extract the nodes from the grid
for (let i = 0; i < rowSize; i++) {
for (let j = 0; j < columnSize; j++) {
nodes.push(new Node(i * columnSize + j, grid[i][j][0], grid[i][j][1]));
}
}
//Randomly generate edges between nodes.
let minDistance = 1.6 * Math.sqrt(height * width / totalNodes)
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
if (distance([nodes[i].x, nodes[i].y], [nodes[j].x, nodes[j].y]) < minDistance) {
nodes[i].adjacent.push(j);
nodes[j].adjacent.push(i);
edges.push([i, j]);
}
}
}
return [nodes, edges];
}

class Graph {
constructor(height, width, totalNodes) {
this.totalNodes = 20;
this.nodes = [];
this.height = height;
this.width = width;
this.totalNodes = totalNodes;
[this.nodes, this.edges] = randomPlanarGraph(this.height, this.width, this.totalNodes);
}

getAdjacent(id) {
return this.nodes[id].adjacent;
}
}

class BreadthFirstSearch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've already written breadth first search for chapter 3; is it possible to reuse that, or make the other version compatible with this one?

Copy link
Contributor Author

@Rishav159 Rishav159 Jul 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The earlier Breadth First Search was simply a 3 line code that takes the problem object and returns the first element from its frontier. Here, the Breadth First Search class performs what the class GraphAgent performed earlier. But in this particular case, I am using generators. Generators prove to be especially useful here since there are 2 bfs running simultaneously and then there is one diagram that extracts nodes from the envelopeiterate()function from Bi-directional Problem.
Additionally, the implementation of the graphs is also different. For example, the getAdjacent function earlier simply scanned the edges list to get the nodes adjacent to a node; but in this case, that will be too costly since there are thousands of nodes. So here, we keep a list of adjacent nodes in the graph node itself. Similarly, there are more fundamental differences between the implementations. Due to all this reasons, I thought trying to reuse the code would get in my way of making the diagram.
What are your thoughts on this part?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok, that makes sense

constructor(graph, initial) {
this.graph = graph;
this.initial = initial;
this.frontier = [this.initial];
//State is true if the node is unexplored and false if explored or in frontier
this.state = new Array(this.graph.nodes.length).fill(true);
this.frontierIndex = 0;
}
//Expands a node from the frontier and returns that node
step() {
if (this.frontier.length <= this.frontierIndex) {
return undefined;
} else {
let nextNode = this.frontier[this.frontierIndex];
//Remove nextNode from frontier
this.frontierIndex++;
//Add to explored
this.state[nextNode] = false;
//Get adjacent nodes which are unexplored
let adjacentNodes = this.graph.getAdjacent(nextNode).filter(x => this.state[x]);
//Mark each adjacent node
adjacentNodes.forEach(x => this.state[x] = false);
//Push to frontier
adjacentNodes.forEach(x => this.frontier.push(x));
return nextNode;
}
}
}

class BidirectionalProblem {
constructor(graph) {
this.graph = graph;
this.initial = 0;
//Force the initial node to be around the middle of the canvas.
for (let i = 0; i < this.graph.nodes.length; i++) {
if (distance([this.graph.width / 2, this.graph.height / 2], [this.graph.nodes[i].x, this.graph.nodes[i].y]) < 50) {
this.initial = i;
break;
}
}
//Final node is chosen randomly
this.final = Math.floor(Math.random() * this.graph.nodes.length);
this.sourceBFS = new BreadthFirstSearch(this.graph, this.initial);
this.destBFS = new BreadthFirstSearch(this.graph, this.final);
}

iterate() {
let obj = {
done: false
}
//Iterate Source side BFS
let nextNode = this.sourceBFS.step();
obj.source = nextNode;
if (!this.destBFS.state[nextNode]) {
obj.done = true;
}

//Iterate Destination side BFS
nextNode = this.destBFS.step();
obj.dest = nextNode;
if (!this.sourceBFS.state[nextNode]) {
obj.done = true;
}
return obj;
}
}
132 changes: 132 additions & 0 deletions 3-Solving-Problems-By-Searching/c_bi-directional.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
class BidirectionalDiagram {
constructor(selector, h, w) {
this.selector = selector;
this.h = h;
this.w = w;
this.selector.select('canvas').remove();
this.root = this.selector
.append('canvas')
.attr('height', this.h)
.attr('width', this.w);
this.context = this.root.node().getContext("2d");
this.context.clearRect(0, 0, this.w, this.h);
this.delay = 4;
}

init(problem, textElement) {
this.problem = problem;
this.nodes = this.problem.graph.nodes;
this.edges = this.problem.graph.edges;
this.initial = this.problem.initial;
this.final = this.problem.final;
this.textElement = textElement;

this.initialColor = 'hsl(0, 20%, 80%)';
this.edgeColor = 'hsl(0, 2%, 80%)';
this.sourceBFSColor = 'hsl(209, 100%, 50%)';
this.destBFSColor = 'hsl(209, 30%, 50%)';
this.sourceColor = 'hsl(209, 100%, 20%)';
this.destColor = 'hsl(209, 100%, 20%)';
this.nodeSize = 3.5;
this.textColorScale = d3.scaleLinear().domain([0, this.nodes.length / 2])
.interpolate(d3.interpolateRgb)
.range([d3.hsl('hsla(102, 100%, 50%, 1)'), d3.hsl('hsla(0, 100%, 50%, 1)')]);

//Draw all nodes
for (let i = 0; i < this.nodes.length; i++) {
this.colorNode(i, this.initialColor);
}
//Draw all edges
for (let i = 0; i < this.edges.length; i++) {
let d = this.edges[i];
this.context.beginPath();
this.context.lineWidth = 1;
this.context.strokeStyle = this.edgeColor;
this.context.moveTo(this.nodes[d[0]].x, this.nodes[d[0]].y);
this.context.lineTo(this.nodes[d[1]].x, this.nodes[d[1]].y);
this.context.stroke();
this.context.closePath();
}

//Initial Node
this.context.fillStyle = this.sourceColor;
this.context.beginPath();
this.context.arc(this.nodes[this.initial].x, this.nodes[this.initial].y, 1.2 * this.nodeSize, 0, 2 * Math.PI, true);
this.context.fill();
this.context.closePath();
this.steps++;
this.textElement.text(this.steps);
//Final Node
this.context.fillStyle = this.destColor;
this.context.beginPath();
this.context.arc(this.nodes[this.final].x, this.nodes[this.final].y, 1.2 * this.nodeSize, 0, 2 * Math.PI, true);
this.context.fill();
this.context.closePath();
this.steps++;
this.textElement.text(this.steps);
this.textElement.style('color', this.textColorScale(this.steps));
this.steps = 0;
this.bfs();
}

colorNode(node, color) {
//If the given node is not an initial node or final node
if (node != this.initial && node != this.final) {
this.context.fillStyle = color;
this.context.beginPath();
this.context.arc(this.nodes[node].x, this.nodes[node].y, this.nodeSize, 0, 2 * Math.PI, true);
this.context.fill();
this.context.closePath();
this.steps++;
//Update steps in the page
this.textElement.style('color', this.textColorScale(this.steps));
this.textElement.text(this.steps);
}
}

bfs() {
this.intervalFunction = setInterval(() => {
let next = this.problem.iterate();

if (next.source) {
this.colorNode(next.source, this.sourceBFSColor)
}
if (next.dest) {
this.colorNode(next.dest, this.destBFSColor)
}
if (next.done) {
clearInterval(this.intervalFunction)
}
}, this.delay);
}
}

class BFSDiagram extends BidirectionalDiagram {
constructor(selector, h, w) {
super(selector, h, w);
}

bfs() {
this.bfsAgent = new BreadthFirstSearch(this.problem.graph, this.initial);
this.intervalFunction = setInterval(() => {
let node = this.bfsAgent.step();
this.colorNode(node, this.sourceBFSColor);
if (node == this.final) {
clearInterval(this.intervalFunction)
}
}, this.delay);
}
}

$(document).ready(function() {
function init() {
let bidirectionalDiagram = new BidirectionalDiagram(d3.select('#backtracking').select('#biCanvas'), 500, 550);
let bfsDiagram = new BFSDiagram(d3.select('#backtracking').select('#bfsCanvas'), 500, 550)
let graph = new Graph(500, 530, 1500);
let problem = new BidirectionalProblem(graph);
bidirectionalDiagram.init(problem, d3.select('#backtracking').select('#biStepCount'));
bfsDiagram.init(problem, d3.select('#backtracking').select('#bfsStepCount'));
}
init();
$('#backtracking .restart-button').click(init);
});
17 changes: 17 additions & 0 deletions 3-Solving-Problems-By-Searching/gitter-grid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function gitteredGrid(height, width, totalNodes) {
let cellSize = Math.sqrt(height * width / totalNodes);
let columnSize = Math.floor(width / cellSize);
let rowSize = Math.floor(height / cellSize);
let grid = [];

for (let i = 0; i < rowSize; i++) {
grid.push(new Array(columnSize));
}

for (let i = 0; i < rowSize; i++) {
for (let j = 0; j < columnSize; j++) {
grid[i][j] = [cellSize * j + cellSize / 6 + Math.random() * cellSize * 2 / 3, cellSize * i + cellSize / 6 + Math.random() * cellSize * 2 / 3];
}
}
return grid;
}
32 changes: 30 additions & 2 deletions 3-Solving-Problems-By-Searching/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
<link rel="stylesheet" href="../styles.css">
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="../main.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/two.js/0.6.0/two.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>

<script type="text/javascript" src="./helpers.js"></script>
<script type="text/javascript" src="./gitter-grid.js"></script>
<script type="text/javascript" src="./aStarSearch.js"></script>
<script type="text/javascript" src="./bestFirstSearch.js"></script>
<script type="text/javascript" src="./breadthFirstSearch.js"></script>
Expand All @@ -18,6 +20,7 @@
<script type="text/javascript" src="./iterativeDeepening.js"></script>
<script type="text/javascript" src="./uniformCostSearch.js"></script>
<script type="text/javascript" src="./costDetails.js"></script>
<script type="text/javascript" src="./bi-directional.js"></script>

<script type="text/javascript" src="./c_aStarSearch.js"></script>
<script type="text/javascript" src="./c_bestFirstSearch.js"></script>
Expand All @@ -28,6 +31,8 @@
<script type="text/javascript" src="./c_uniformCostSearch.js"></script>
<script type="text/javascript" src="./c_nodeExpansion.js"></script>
<script type="text/javascript" src="./c_costDetails.js"></script>
<script type="text/javascript" src="./c_bi-directional.js"></script>

</head>

<body>
Expand Down Expand Up @@ -360,6 +365,29 @@ <h4>Depth Limit : </h4>
</div>
</div>
</div>
<h2>Bi-directional BFS</h2>
<p>In bi-directional BFS, we run two simultaneous searches, one from the initial state and one from the goal state. We stop when these searches meet in the middle (ie, a node is explored by both the BFS)</p>
<p>The motivation behind this is that <b>b<sup>d/2</sup> + b<sup>d/2</sup> is smaller than b<sup>d</sup></b></p>
<p>In the diagram below, we compare the performance between bi-directional BFS and standard BFS side by side. The total number of nodes generated for each strategy is given below the diagram.</p>
<p>Notice that the further apart the final node is from the initial node, the better bi-directional BFS performs (as compared to standard BFS). Use the restart button to restart the simulation.</p>
<div id='backtracking'>
<div class="row">
<div class='btn btn-primary restart-button'>Restart</div>
</div>
<div class="row" style="margin-top:1%">
<div class="col-md-6">
<h2 style="text-align:center;">Bi-directional BFS</h2>
<div class="canvas" id='biCanvas' height="300px"></div>
<h1 id = 'biStepCount' style="text-align:center;margin-top:0%;"></h1>
</div>
<div class="col-md-6">
<h2 style="text-align:center;">Standard BFS</h2>
<div class="canvas" id='bfsCanvas' height="300px"></div>
<h1 id = 'bfsStepCount' style="text-align:center;margin-top:0%;"></h1>
</div>
</div>
</div>


<h2>A Star Search</h2>
<p>Click on the canvas to restart the simulation</p>
Expand Down