Skip to content

Commit 67cdad8

Browse files
committed
Add graph.
1 parent 840635e commit 67cdad8

File tree

8 files changed

+426
-1
lines changed

8 files changed

+426
-1
lines changed

Diff for: .eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"rules": {
99
"no-bitwise": "off",
1010
"no-lonely-if": "off",
11-
"class-methods-use-this": "off"
11+
"class-methods-use-this": "off",
12+
"arrow-body-style": "off"
1213
}
1314
}

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
8. [Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree)
1616
* [Binary Search Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/binary-search-tree)
1717
* [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree)
18+
9. [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph)
1819

1920
## [Algorithms](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms)
2021

Diff for: src/data-structures/graph/Graph.js

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
export default class Graph {
2+
/**
3+
* @param isDirected {boolean}
4+
*/
5+
constructor(isDirected = false) {
6+
this.vertices = {};
7+
this.isDirected = isDirected;
8+
}
9+
10+
/**
11+
* @param newVertex {GraphVertex}
12+
* @returns {Graph}
13+
*/
14+
addVertex(newVertex) {
15+
this.vertices[newVertex.getKey()] = newVertex;
16+
17+
return this;
18+
}
19+
20+
/**
21+
* @param vertexKey {string}
22+
* @returns GraphVertex
23+
*/
24+
getVertexByKey(vertexKey) {
25+
return this.vertices[vertexKey];
26+
}
27+
28+
/**
29+
* @param edge {GraphEdge}
30+
* @returns {Graph}
31+
*/
32+
addEdge(edge) {
33+
// Try to find and end start vertices.
34+
let startVertex = this.getVertexByKey(edge.startVertex.getKey());
35+
let endVertex = this.getVertexByKey(edge.endVertex.getKey());
36+
37+
// Insert start vertex if it wasn't inserted.
38+
if (!startVertex) {
39+
this.addVertex(edge.startVertex);
40+
startVertex = this.getVertexByKey(edge.startVertex.getKey());
41+
}
42+
43+
// Insert end vertex if it wasn't inserted.
44+
if (!endVertex) {
45+
this.addVertex(edge.endVertex);
46+
endVertex = this.getVertexByKey(edge.endVertex.getKey());
47+
}
48+
49+
// @TODO: Check if edge has been already added.
50+
51+
// Add edge to the vertices.
52+
if (this.isDirected) {
53+
// If graph IS directed then add the edge only to start vertex.
54+
startVertex.addEdge(edge);
55+
} else {
56+
// If graph ISN'T directed then add the edge to both vertices.
57+
startVertex.addEdge(edge);
58+
endVertex.addEdge(edge);
59+
}
60+
61+
return this;
62+
}
63+
64+
/**
65+
* @param startVertex {GraphVertex}
66+
* @param endVertex {GraphVertex}
67+
*/
68+
findEdge(startVertex, endVertex) {
69+
const vertex = this.getVertexByKey(startVertex.getKey());
70+
return vertex.findEdge(endVertex);
71+
}
72+
73+
/**
74+
* @param vertexKey {string}
75+
* @returns {GraphVertex}
76+
*/
77+
findVertexByKey(vertexKey) {
78+
if (this.vertices[vertexKey]) {
79+
return this.vertices[vertexKey];
80+
}
81+
82+
return null;
83+
}
84+
85+
toString() {
86+
return Object.keys(this.vertices).toString();
87+
}
88+
}

Diff for: src/data-structures/graph/GraphEdge.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default class GraphEdge {
2+
/**
3+
* @param startVertex {GraphVertex}
4+
* @param endVertex {GraphVertex}
5+
* @param weight {number}
6+
*/
7+
constructor(startVertex, endVertex, weight = 1) {
8+
this.startVertex = startVertex;
9+
this.endVertex = endVertex;
10+
this.weight = weight;
11+
}
12+
}

Diff for: src/data-structures/graph/GraphVertex.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import LinkedList from '../linked-list/LinkedList';
2+
3+
export default class GraphVertex {
4+
constructor(value) {
5+
if (value === undefined) {
6+
throw new Error('Graph vertex must have a value');
7+
}
8+
9+
// Normally you would store string value like vertex name.
10+
// But generally it may be any object as well
11+
this.value = value;
12+
this.edges = new LinkedList();
13+
}
14+
15+
/**
16+
* @param edge {GraphEdge}
17+
* @returns {GraphVertex}
18+
*/
19+
addEdge(edge) {
20+
this.edges.append(edge);
21+
22+
return this;
23+
}
24+
25+
getNeighbors() {
26+
const edges = this.edges.toArray();
27+
28+
const neighborsConverter = ({ value }) => {
29+
return value.startVertex === this ? value.endVertex : value.startVertex;
30+
};
31+
32+
// Return either start or end vertex.
33+
// For undirected graphs it is possible that current vertex will be the end one.
34+
return edges.map(neighborsConverter);
35+
}
36+
37+
/**
38+
* @param requiredEdge {GraphEdge}
39+
* @returns {boolean}
40+
*/
41+
hasEdge(requiredEdge) {
42+
const edgeNode = this.edges.find({
43+
callback: edge => edge === requiredEdge,
44+
});
45+
46+
return !!edgeNode;
47+
}
48+
49+
/**
50+
* @param vertex {GraphVertex}
51+
* @returns {boolean}
52+
*/
53+
hasNeighbor(vertex) {
54+
const vertexNode = this.edges.find({
55+
callback: edge => edge.startVertex === vertex || edge.endVertex === vertex,
56+
});
57+
58+
return !!vertexNode;
59+
}
60+
61+
findEdge(vertex) {
62+
const edgeFinder = (edge) => {
63+
return edge.startVertex === vertex || edge.endVertex === vertex;
64+
};
65+
66+
const edge = this.edges.find({ callback: edgeFinder });
67+
68+
return edge ? edge.value : null;
69+
}
70+
71+
/**
72+
* @returns {string}
73+
*/
74+
getKey() {
75+
return this.value;
76+
}
77+
78+
/**
79+
* @param callback {function}
80+
* @returns {string}
81+
*/
82+
toString(callback) {
83+
return callback ? callback(this.value) : `${this.value}`;
84+
}
85+
}

Diff for: src/data-structures/graph/__test__/Graph.test.js

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import Graph from '../Graph';
2+
import GraphVertex from '../GraphVertex';
3+
import GraphEdge from '../GraphEdge';
4+
5+
describe('Graph', () => {
6+
it('should add vertices to graph', () => {
7+
const graph = new Graph();
8+
9+
const vertexA = new GraphVertex('A');
10+
const vertexB = new GraphVertex('B');
11+
12+
graph
13+
.addVertex(vertexA)
14+
.addVertex(vertexB);
15+
16+
expect(graph.toString()).toBe('A,B');
17+
expect(graph.getVertexByKey(vertexA.getKey())).toEqual(vertexA);
18+
expect(graph.getVertexByKey(vertexB.getKey())).toEqual(vertexB);
19+
});
20+
21+
it('should add edges to undirected graph', () => {
22+
const graph = new Graph();
23+
24+
const vertexA = new GraphVertex('A');
25+
const vertexB = new GraphVertex('B');
26+
27+
const edgeAB = new GraphEdge(vertexA, vertexB);
28+
29+
graph.addEdge(edgeAB);
30+
31+
const graphVertexA = graph.findVertexByKey(vertexA.getKey());
32+
const graphVertexB = graph.findVertexByKey(vertexB.getKey());
33+
34+
expect(graph.toString()).toBe('A,B');
35+
expect(graphVertexA).toBeDefined();
36+
expect(graphVertexB).toBeDefined();
37+
38+
expect(graph.findVertexByKey('not existing')).toBeNull();
39+
40+
expect(graphVertexA.getNeighbors().length).toBe(1);
41+
expect(graphVertexA.getNeighbors()[0]).toEqual(vertexB);
42+
expect(graphVertexA.getNeighbors()[0]).toEqual(graphVertexB);
43+
44+
expect(graphVertexB.getNeighbors().length).toBe(1);
45+
expect(graphVertexB.getNeighbors()[0]).toEqual(vertexA);
46+
expect(graphVertexB.getNeighbors()[0]).toEqual(graphVertexA);
47+
});
48+
49+
it('should add edges to directed graph', () => {
50+
const graph = new Graph(true);
51+
52+
const vertexA = new GraphVertex('A');
53+
const vertexB = new GraphVertex('B');
54+
55+
const edgeAB = new GraphEdge(vertexA, vertexB);
56+
57+
graph.addEdge(edgeAB);
58+
59+
const graphVertexA = graph.findVertexByKey(vertexA.getKey());
60+
const graphVertexB = graph.findVertexByKey(vertexB.getKey());
61+
62+
expect(graph.toString()).toBe('A,B');
63+
expect(graphVertexA).toBeDefined();
64+
expect(graphVertexB).toBeDefined();
65+
66+
expect(graphVertexA.getNeighbors().length).toBe(1);
67+
expect(graphVertexA.getNeighbors()[0]).toEqual(vertexB);
68+
expect(graphVertexA.getNeighbors()[0]).toEqual(graphVertexB);
69+
70+
expect(graphVertexB.getNeighbors().length).toBe(0);
71+
});
72+
73+
it('should find edge by vertices in undirected graph', () => {
74+
const graph = new Graph();
75+
76+
const vertexA = new GraphVertex('A');
77+
const vertexB = new GraphVertex('B');
78+
const vertexC = new GraphVertex('C');
79+
80+
const edgeAB = new GraphEdge(vertexA, vertexB, 10);
81+
82+
graph.addEdge(edgeAB);
83+
84+
const graphEdgeAB = graph.findEdge(vertexA, vertexB);
85+
const graphEdgeBA = graph.findEdge(vertexB, vertexA);
86+
const graphEdgeAC = graph.findEdge(vertexB, vertexC);
87+
88+
expect(graphEdgeAC).toBeNull();
89+
expect(graphEdgeAB).toEqual(edgeAB);
90+
expect(graphEdgeBA).toEqual(edgeAB);
91+
expect(graphEdgeAB.weight).toBe(10);
92+
});
93+
94+
it('should find edge by vertices in directed graph', () => {
95+
const graph = new Graph(true);
96+
97+
const vertexA = new GraphVertex('A');
98+
const vertexB = new GraphVertex('B');
99+
const vertexC = new GraphVertex('C');
100+
101+
const edgeAB = new GraphEdge(vertexA, vertexB, 10);
102+
103+
graph.addEdge(edgeAB);
104+
105+
const graphEdgeAB = graph.findEdge(vertexA, vertexB);
106+
const graphEdgeBA = graph.findEdge(vertexB, vertexA);
107+
const graphEdgeAC = graph.findEdge(vertexB, vertexC);
108+
109+
expect(graphEdgeAC).toBeNull();
110+
expect(graphEdgeBA).toBeNull();
111+
expect(graphEdgeAB).toEqual(edgeAB);
112+
expect(graphEdgeAB.weight).toBe(10);
113+
});
114+
});

Diff for: src/data-structures/graph/__test__/GraphEdge.test.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import GraphEdge from '../GraphEdge';
2+
import GraphVertex from '../GraphVertex';
3+
4+
describe('GraphEdge', () => {
5+
it('should create graph edge with default weight', () => {
6+
const startVertex = new GraphVertex('A');
7+
const endVertex = new GraphVertex('B');
8+
const edge = new GraphEdge(startVertex, endVertex);
9+
10+
expect(edge.startVertex).toEqual(startVertex);
11+
expect(edge.endVertex).toEqual(endVertex);
12+
expect(edge.weight).toEqual(1);
13+
});
14+
15+
it('should create graph edge with predefined weight', () => {
16+
const startVertex = new GraphVertex('A');
17+
const endVertex = new GraphVertex('B');
18+
const edge = new GraphEdge(startVertex, endVertex, 10);
19+
20+
expect(edge.startVertex).toEqual(startVertex);
21+
expect(edge.endVertex).toEqual(endVertex);
22+
expect(edge.weight).toEqual(10);
23+
});
24+
});

0 commit comments

Comments
 (0)