Skip to content

Commit eca4458

Browse files
authored
Merge pull request #14 from linux-nerd/feature/graph
Feature/graph
2 parents daf7da4 + 04f146e commit eca4458

File tree

6 files changed

+397
-15
lines changed

6 files changed

+397
-15
lines changed

src/binary-search-tree/binary-search-tree.js

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
* @return BSTNode
88
*/
99
export class BSTNode {
10-
constructor(key, left = null, right = null) {
10+
constructor(key, details = null, left = null, right = null) {
1111
// the constructor creates the leaf node
1212
this._key = key;
13+
this._details = details;
1314
this._left = left;
1415
this._right = right;
1516
}
@@ -18,6 +19,10 @@ export class BSTNode {
1819
get key() { return this._key; }
1920
set key(key) { this._key = key; }
2021

22+
/* Getter and Setter for key */
23+
get details() { return this._details; }
24+
set details(details) { this._details = details; }
25+
2126
/* Getter and Setter for left sub tree */
2227
get left() { return this._left; }
2328
set left(left) { this._left = left; }
@@ -56,9 +61,9 @@ export class BST {
5661
* Insert value in the BST
5762
* @param {*} val
5863
*/
59-
insert(val) {
64+
insert(val, details = null) {
6065
// create a BST node
61-
const bstNode = new BSTNode(val);
66+
const bstNode = new BSTNode(val, details);
6267

6368
/**
6469
* @name recurseBST
@@ -97,8 +102,13 @@ export class BST {
97102
// when the node has no children or when its a leaf
98103
// then simply delete the node
99104
if (!findNode.currentNode.left && !findNode.currentNode.right) {
100-
const direction = findNode.parentNode.key > val ? 'left' : 'right';
101-
findNode.parentNode[direction] = null;
105+
//check if the node is the root node
106+
if (findNode.parentNode === null) {
107+
this.root = null;
108+
} else {
109+
const direction = findNode.parentNode.key > val ? 'left' : 'right';
110+
findNode.parentNode[direction] = null;
111+
}
102112
this[length]--;
103113
}
104114
// case 2
@@ -150,14 +160,16 @@ export class BST {
150160
lookup(val) {
151161
let response = { hasVal: false, currentNode: null, parentNode: null };
152162
const lookRecursively = (node = this.root, parent = null) => {
153-
if (node.key === val) {
154-
response.hasVal = true;
155-
response.currentNode = node;
156-
response.parentNode = parent;
157-
} else if (node.left && node.key > val) {
158-
lookRecursively(node.left, node);
159-
} else if (node.right && node.key < val) {
160-
lookRecursively(node.right, node);
163+
if (node) {
164+
if (node.key === val) {
165+
response.hasVal = true;
166+
response.currentNode = node;
167+
response.parentNode = parent;
168+
} else if (node.left && node.key > val) {
169+
lookRecursively(node.left, node);
170+
} else if (node.right && node.key < val) {
171+
lookRecursively(node.right, node);
172+
}
161173
}
162174
}
163175

src/graph/adj-list.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { BST } from '../binary-search-tree/binary-search-tree';
2+
3+
export class AdjacencyList {
4+
constructor(isDiGraph) {
5+
this.diGraph = isDiGraph;
6+
this.vertices = new Map();
7+
}
8+
9+
/**
10+
* It adds node to the map and assign an empty list
11+
* If the node is already added to the map, then nothing happens
12+
* @param {string|number} node
13+
*/
14+
addNode(node) {
15+
if (!this.vertices.get(node)) {
16+
this.vertices.set(node, new BST);
17+
}
18+
}
19+
20+
/**
21+
* It removes the node from the map
22+
* @param {string|number} node
23+
*/
24+
removeNode(node) {
25+
// Remove all the vertices formed by this node
26+
this.vertices.forEach((val, key) => {
27+
this.removeEdge(key, node);
28+
});
29+
30+
// finally remove the node
31+
this.vertices.delete(node);
32+
}
33+
34+
/**
35+
* It adds an edge from vertex fromVertex to vertex toVertex
36+
* If the verticies are not present then it first adds the missing vertex
37+
* If the graph is a diGraph then it will add edge between toVertex and fromVertex
38+
*
39+
* @param {string|number} fromVertex
40+
* @param {string|number} toVertex
41+
* @param {number} weight
42+
*/
43+
addEdge(fromVertex, toVertex, weight) {
44+
if (!this.vertices.has(fromVertex)) {
45+
this.addNode(fromVertex);
46+
}
47+
48+
if (!this.vertices.has(toVertex)) {
49+
this.addNode(toVertex);
50+
}
51+
52+
this.vertices.get(fromVertex).insert(toVertex, { weight });
53+
54+
if (!this.diGraph) {
55+
this.vertices.get(toVertex).insert(fromVertex, { weight });
56+
}
57+
}
58+
59+
/**
60+
* Remove an edge from start vertex to end vertex
61+
* @param {string|number} fromVertex
62+
* @param {string|number} toVertex
63+
*/
64+
removeEdge(fromVertex, toVertex) {
65+
if (this.vertices.has(fromVertex)) {
66+
const deleteEdge = this.vertices.get(fromVertex).delete(toVertex);
67+
if (deleteEdge && deleteEdge.constructor === Error) {
68+
return new Error(`No edge present between ${fromVertex} and ${toVertex}`);
69+
}
70+
} else {
71+
return new Error(`No edge present between ${fromVertex} and ${toVertex}`);
72+
}
73+
74+
// if the graph is undirected and id its the first call to the removeEdge method
75+
// set stopRecursion to true and call removeEdge method again by
76+
// swapping the parameters.
77+
if (!this.diGraph && !this.removeEdge.stopRecursion) {
78+
this.removeEdge.stopRecursion = true;
79+
this.removeEdge(toVertex, fromVertex);
80+
} else {
81+
if (this.removeEdge.stopRecursion) {
82+
this.removeEdge.stopRecursion = undefined;
83+
}
84+
}
85+
}
86+
87+
/**
88+
* It returns the weight of the edge. If the edge is not found then it returns undefined
89+
* @param {string|number} fromVertex
90+
* @param {string|number} toVertex
91+
*/
92+
getEdgeWeight(fromVertex, toVertex) {
93+
let weight;
94+
if (this.vertices.has(fromVertex)) {
95+
const lookup = this.vertices.get(fromVertex).lookup(toVertex);
96+
weight = lookup.hasVal ? lookup.currentNode.details.weight : void 0;
97+
}
98+
return weight ? weight : new Error(`Edge not found between ${fromVertex} and ${toVertex}`);
99+
}
100+
}

src/graph/adj-list.spec.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { AdjacencyList } from './adj-list';
2+
3+
describe('Adjacency List', () => {
4+
let adjList;
5+
beforeEach(() => {
6+
adjList = new AdjacencyList(true);
7+
});
8+
9+
it('should create adjacency list object', () => {
10+
expect(adjList).toBeDefined();
11+
expect(adjList.vertices).toBeDefined();
12+
});
13+
14+
it('should have empty vertices when adjacency list object is created', () => {
15+
expect(adjList.vertices.size).toBe(0);
16+
});
17+
18+
it('should add node through addNode method', () => {
19+
expect(adjList.vertices.size).toBe(0);
20+
21+
adjList.addNode('A');
22+
expect(adjList.vertices.size).toBe(1);
23+
expect(adjList.vertices.get('A').len).toBe(0);
24+
25+
adjList.addNode('B');
26+
expect(adjList.vertices.size).toBe(2);
27+
expect(adjList.vertices.get('B').len).toBe(0);
28+
});
29+
30+
it('should delete the node from the map', () => {
31+
expect(adjList.vertices.size).toBe(0);
32+
33+
adjList.addNode('A');
34+
expect(adjList.vertices.size).toBe(1);
35+
36+
adjList.removeNode('A');
37+
expect(adjList.vertices.size).toBe(0);
38+
expect(adjList.vertices.has('A')).toBeFalsy();
39+
});
40+
41+
describe('Insert Operation', () => {
42+
let unDirectedGraph;
43+
beforeEach(() => {
44+
unDirectedGraph = new AdjacencyList(false);
45+
46+
adjList.addNode('A');
47+
adjList.addNode('B');
48+
});
49+
50+
it('should add an edge of weight 20 between A and B', () => {
51+
adjList.addEdge('A', 'B', 200);
52+
const vertexA = adjList.vertices.get('A');
53+
expect(vertexA.len).toBe(1);
54+
55+
const lookupB = vertexA.lookup('B');
56+
expect(lookupB.hasVal).toBeTruthy();
57+
expect(lookupB.currentNode.details.weight).toBe(200);
58+
});
59+
it('should add edge between B and C', () => {
60+
expect(adjList.vertices.size).toBe(2);
61+
62+
adjList.addEdge('B', 'C');
63+
expect(adjList.vertices.size).toBe(3);
64+
const vertexB = adjList.vertices.get('B');
65+
expect(vertexB.len).toBe(1);
66+
67+
const lookupC = vertexB.lookup('C');
68+
expect(lookupC.hasVal).toBeTruthy();
69+
expect(lookupC.currentNode.details.weight).toBe(undefined);
70+
});
71+
it('should add edge A and B in undirected graph', () => {
72+
expect(unDirectedGraph.vertices.size).toBe(0);
73+
74+
unDirectedGraph.addEdge('A', 'B');
75+
expect(unDirectedGraph.vertices.size).toBe(2);
76+
77+
const vertexA = unDirectedGraph.vertices.get('A');
78+
expect(vertexA.len).toBe(1);
79+
const lookupB = vertexA.lookup('B');
80+
expect(lookupB.hasVal).toBeTruthy();
81+
82+
const vertexB = unDirectedGraph.vertices.get('B');
83+
expect(vertexB.len).toBe(1);
84+
const lookupA = vertexB.lookup('A');
85+
expect(lookupA.hasVal).toBeTruthy();
86+
});
87+
});
88+
89+
describe('Delete Edge Operation', () => {
90+
beforeEach(() => {
91+
adjList.addEdge('A', 'B');
92+
adjList.addEdge('A', 'C');
93+
});
94+
95+
it('should delete edge A -> C', () => {
96+
expect(adjList.vertices.size).toBe(3);
97+
const vertexA = adjList.vertices.get('A');
98+
expect(vertexA.len).toBe(2);
99+
expect(vertexA.lookup('C').hasVal).toBeTruthy();
100+
adjList.removeEdge('A', 'C');
101+
expect(vertexA.len).toBe(1);
102+
expect(vertexA.lookup('C').hasVal).toBeFalsy();
103+
});
104+
it('should return error while deleting edge D -> A', () => {
105+
expect(adjList.removeEdge('D', 'A').message).toBe('No edge present between D and A');
106+
});
107+
108+
describe('For undirectional Graph', () => {
109+
let undirectedList;
110+
beforeEach(() => {
111+
undirectedList = new AdjacencyList(false);
112+
undirectedList.addEdge('A', 'B');
113+
undirectedList.addEdge('A', 'C');
114+
});
115+
116+
it('should delete edge A -> C and edge C -> A', () => {
117+
expect(undirectedList.vertices.size).toBe(3);
118+
const msg = undirectedList.removeEdge('A', 'C');
119+
expect(undirectedList.vertices.get('A').lookup('C').hasVal).toBeFalsy();
120+
expect(undirectedList.vertices.get('C').lookup('A').hasVal).toBeFalsy();
121+
expect(msg).toBe(void 0);
122+
});
123+
});
124+
});
125+
126+
describe('Delete Vertex Operation', () => {
127+
beforeEach(() => {
128+
adjList.addEdge('A', 'B');
129+
adjList.addEdge('A', 'C');
130+
});
131+
132+
it('should delete vertex C and finally edge A -> C', () => {
133+
expect(adjList.vertices.size).toBe(3);
134+
expect(adjList.vertices.get('A').len).toBe(2);
135+
expect(adjList.vertices.get('A').lookup('C').hasVal).toBeTruthy();
136+
137+
adjList.removeNode('C');
138+
expect(adjList.vertices.size).toBe(2);
139+
expect(adjList.vertices.get('A').len).toBe(1);
140+
expect(adjList.vertices.get('A').lookup('C').hasVal).toBeFalsy();
141+
});
142+
143+
describe('For undirectional Graph', () => {
144+
let undirectedList;
145+
beforeEach(() => {
146+
undirectedList = new AdjacencyList(false);
147+
undirectedList.addEdge('A', 'B');
148+
undirectedList.addEdge('A', 'C');
149+
});
150+
151+
it('should delete node C and also vertices A -> C and C -> A', () => {
152+
expect(undirectedList.vertices.size).toBe(3);
153+
expect(undirectedList.vertices.get('A').len).toBe(2);
154+
expect(undirectedList.vertices.get('A').lookup('C').hasVal).toBeTruthy();
155+
expect(undirectedList.vertices.get('C').len).toBe(1);
156+
expect(undirectedList.vertices.get('C').lookup('A').hasVal).toBeTruthy();
157+
158+
undirectedList.removeNode('C');
159+
160+
expect(undirectedList.vertices.size).toBe(2);
161+
expect(undirectedList.vertices.get('A').len).toBe(1);
162+
expect(undirectedList.vertices.get('A').lookup('C').hasVal).toBeFalsy();
163+
expect(undirectedList.vertices.has('C')).toBeFalsy();
164+
});
165+
});
166+
});
167+
168+
describe('Get edge weight', () => {
169+
beforeEach(() => {
170+
adjList.addEdge('A', 'B', 200);
171+
adjList.addEdge('A', 'C', 150);
172+
adjList.addEdge('B', 'C', 250);
173+
});
174+
175+
it('should get 200 as weight between edge A and B', () => {
176+
const weight = adjList.getEdgeWeight('A', 'B');
177+
expect(weight).toBe(200);
178+
});
179+
it('should get 150 as weight between edge A and C', () => {
180+
const weight = adjList.getEdgeWeight('A', 'C');
181+
expect(weight).toBe(150);
182+
});
183+
it('should get Error for weight between edge A and D', () => {
184+
const weight = adjList.getEdgeWeight('A', 'D');
185+
expect(weight.message).toBe('Edge not found between A and D');
186+
});
187+
it('should get Error for weight between edge D and A', () => {
188+
const weight = adjList.getEdgeWeight('D', 'A');
189+
expect(weight.message).toBe('Edge not found between D and A');
190+
});
191+
});
192+
});

0 commit comments

Comments
 (0)