Skip to content

Commit 204612d

Browse files
authored
Merge pull request #1 from emyann/feat/add-linkedlist-graph
feat: add LinkedList and Graph data structures
2 parents 02d559a + 81a15ca commit 204612d

File tree

13 files changed

+611
-2
lines changed

13 files changed

+611
-2
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ workflows:
4444
version: 2
4545
build:
4646
jobs:
47-
- test
47+
- build
4848
- publish:
4949
requires:
50-
- test
50+
- build
5151
filters:
5252
branches:
5353
only:

src/data-structures/Graph/Graph.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { GraphVertex } from './GraphVertex';
2+
import { GraphEdge } from './GraphEdge';
3+
4+
export class Graph<TVertex, TEdge> {
5+
vertices: Record<string | number, GraphVertex<TVertex, TEdge>>;
6+
edges: Record<string, GraphEdge<TVertex, TEdge>>;
7+
constructor(public isDirected = false) {
8+
this.vertices = {};
9+
this.edges = {};
10+
}
11+
12+
addVertex(newVertex: GraphVertex<TVertex, TEdge>) {
13+
this.vertices[newVertex.getKey()] = newVertex;
14+
15+
return this;
16+
}
17+
18+
getVertexByKey(vertexKey: string | number) {
19+
return this.vertices[vertexKey];
20+
}
21+
22+
getNeighbors(vertex: GraphVertex<TVertex, TEdge>) {
23+
return vertex.getNeighbors();
24+
}
25+
26+
getAllVertices() {
27+
return Object.values(this.vertices);
28+
}
29+
30+
getAllEdges() {
31+
return Object.values(this.edges);
32+
}
33+
34+
addEdge(edge: GraphEdge<TVertex, TEdge>) {
35+
// Try to find and end start vertices.
36+
let startVertex = this.getVertexByKey(edge.startVertex.getKey());
37+
let endVertex = this.getVertexByKey(edge.endVertex.getKey());
38+
39+
// Insert start vertex if it wasn't inserted.
40+
if (!startVertex) {
41+
this.addVertex(edge.startVertex);
42+
startVertex = this.getVertexByKey(edge.startVertex.getKey());
43+
}
44+
45+
// Insert end vertex if it wasn't inserted.
46+
if (!endVertex) {
47+
this.addVertex(edge.endVertex);
48+
endVertex = this.getVertexByKey(edge.endVertex.getKey());
49+
}
50+
51+
// Check if edge has been already added.
52+
if (this.edges[edge.getKey()]) {
53+
throw new Error('Edge has already been added before');
54+
} else {
55+
this.edges[edge.getKey()] = edge;
56+
}
57+
58+
// Add edge to the vertices.
59+
if (this.isDirected) {
60+
// If graph IS directed then add the edge only to start vertex.
61+
startVertex.addEdge(edge);
62+
} else {
63+
// If graph ISN'T directed then add the edge to both vertices.
64+
startVertex.addEdge(edge);
65+
endVertex.addEdge(edge);
66+
}
67+
68+
return this;
69+
}
70+
71+
/**
72+
* @param {GraphEdge} edge
73+
*/
74+
deleteEdge(edge: GraphEdge<TVertex, TEdge>) {
75+
// Delete edge from the list of edges.
76+
if (this.edges[edge.getKey()]) {
77+
delete this.edges[edge.getKey()];
78+
} else {
79+
throw new Error('Edge not found in graph');
80+
}
81+
82+
// Try to find and end start vertices and delete edge from them.
83+
const startVertex = this.getVertexByKey(edge.startVertex.getKey());
84+
const endVertex = this.getVertexByKey(edge.endVertex.getKey());
85+
86+
startVertex.deleteEdge(edge);
87+
endVertex.deleteEdge(edge);
88+
}
89+
90+
findEdge(startVertex: GraphVertex<TVertex, TEdge>, endVertex: GraphVertex<TVertex, TEdge>) {
91+
const vertex = this.getVertexByKey(startVertex.getKey());
92+
93+
if (!vertex) {
94+
return null;
95+
}
96+
97+
return vertex.findEdge(endVertex);
98+
}
99+
100+
// getEdgeValue() {
101+
// return this.getAllEdges().reduce((weight, graphEdge) => {
102+
// return weight + graphEdge.weight;
103+
// }, 0);
104+
// }
105+
106+
reverse() {
107+
this.getAllEdges().forEach(edge => {
108+
// Delete straight edge from graph and from vertices.
109+
this.deleteEdge(edge);
110+
111+
// Reverse the edge.
112+
edge.reverse();
113+
114+
// Add reversed edge back to the graph and its vertices.
115+
this.addEdge(edge);
116+
});
117+
118+
return this;
119+
}
120+
121+
getVerticesIndices() {
122+
const verticesIndices: Record<string | number, number> = {};
123+
this.getAllVertices().forEach((vertex, index) => {
124+
verticesIndices[vertex.getKey()] = index;
125+
});
126+
127+
return verticesIndices;
128+
}
129+
130+
getAdjacencyMatrix() {
131+
const vertices = this.getAllVertices();
132+
const verticesIndices = this.getVerticesIndices();
133+
134+
// Init matrix with infinities meaning that there is no ways of
135+
// getting from one vertex to another yet.
136+
const adjacencyMatrix = Array(vertices.length)
137+
.fill(null)
138+
.map(() => {
139+
return Array(vertices.length).fill(Infinity);
140+
});
141+
142+
// Fill the columns.
143+
vertices.forEach((vertex, vertexIndex) => {
144+
vertex.getNeighbors().forEach(neighbor => {
145+
const neighborIndex = verticesIndices[neighbor.getKey()];
146+
adjacencyMatrix[vertexIndex][neighborIndex] = this.findEdge(vertex, neighbor)?.value;
147+
});
148+
});
149+
150+
return adjacencyMatrix;
151+
}
152+
153+
/**
154+
* @return {string}
155+
*/
156+
toString() {
157+
return Object.keys(this.vertices).toString();
158+
}
159+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { GraphVertex } from './GraphVertex';
2+
3+
export class GraphEdge<TVertex, TEdge> {
4+
constructor(public startVertex: GraphVertex<TVertex, TEdge>, public endVertex: GraphVertex<TVertex, TEdge>, public value: TEdge) {}
5+
6+
getKey() {
7+
const startVertexKey = this.startVertex.getKey();
8+
const endVertexKey = this.endVertex.getKey();
9+
10+
return `${startVertexKey}_${endVertexKey}`;
11+
}
12+
13+
/**
14+
* @return {GraphEdge}
15+
*/
16+
reverse() {
17+
const tmp = this.startVertex;
18+
this.startVertex = this.endVertex;
19+
this.endVertex = tmp;
20+
21+
return this;
22+
}
23+
24+
/**
25+
* @return {string}
26+
*/
27+
toString() {
28+
return this.getKey();
29+
}
30+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { LinkedList } from '../LinkedList/LinkedList';
2+
import { CompareFunction } from '../../utilities/Comparator';
3+
import { GraphEdge } from './GraphEdge';
4+
import { LinkedListNode } from '../LinkedList/LinkedListNode';
5+
6+
export type VertexKeyExtractor<T> = (vertex: T) => string | number;
7+
8+
export class GraphVertex<TVertex, TEdge> {
9+
value: TVertex;
10+
edges: LinkedList<GraphEdge<TVertex, TEdge>>;
11+
constructor(value: TVertex, public keyExtractor: VertexKeyExtractor<TVertex>) {
12+
if (value === undefined) {
13+
throw new Error('Graph vertex must have a value');
14+
}
15+
16+
const edgeComparator: CompareFunction<GraphEdge<TVertex, TEdge>> = (edgeA, edgeB) => {
17+
if (edgeA.getKey() === edgeB.getKey()) {
18+
return 0;
19+
}
20+
21+
return edgeA.getKey() < edgeB.getKey() ? -1 : 1;
22+
};
23+
24+
// Normally you would store string value like vertex name.
25+
// But generally it may be any object as well
26+
this.value = value;
27+
this.edges = new LinkedList({ compareFunction: edgeComparator });
28+
}
29+
30+
addEdge(edge: GraphEdge<TVertex, TEdge>) {
31+
this.edges.append(edge);
32+
33+
return this;
34+
}
35+
36+
deleteEdge(edge: GraphEdge<TVertex, TEdge>) {
37+
this.edges.delete(edge);
38+
}
39+
40+
getNeighbors(): GraphVertex<TVertex, TEdge>[] {
41+
const edges = this.edges.toArray();
42+
43+
const neighborsConverter = (node: LinkedListNode<GraphEdge<TVertex, TEdge>>) => {
44+
return node.value.startVertex === this ? node.value.endVertex : node.value.startVertex;
45+
};
46+
47+
// Return either start or end vertex.
48+
// For undirected graphs it is possible that current vertex will be the end one.
49+
return edges.map(neighborsConverter);
50+
}
51+
52+
getEdges() {
53+
return this.edges.toArray().map(linkedListNode => linkedListNode.value);
54+
}
55+
56+
getDegree() {
57+
return this.edges.toArray().length;
58+
}
59+
60+
hasEdge(requiredEdge: GraphEdge<TVertex, TEdge>) {
61+
const edgeNode = this.edges.find({
62+
callback: edge => edge === requiredEdge
63+
});
64+
65+
return !!edgeNode;
66+
}
67+
68+
hasNeighbor(vertex: GraphVertex<TVertex, TEdge>) {
69+
const vertexNode = this.edges.find({
70+
callback: edge => edge.startVertex === vertex || edge.endVertex === vertex
71+
});
72+
73+
return !!vertexNode;
74+
}
75+
76+
findEdge(vertex: GraphVertex<TVertex, TEdge>) {
77+
const edgeFinder = (edge: GraphEdge<TVertex, TEdge>) => {
78+
return edge.startVertex === vertex || edge.endVertex === vertex;
79+
};
80+
81+
const edge = this.edges.find({ callback: edgeFinder });
82+
83+
return edge ? edge.value : null;
84+
}
85+
86+
getKey() {
87+
return this.keyExtractor(this.value);
88+
}
89+
90+
deleteAllEdges() {
91+
this.getEdges().forEach(edge => this.deleteEdge(edge));
92+
93+
return this;
94+
}
95+
96+
toString(callback: (value: TVertex) => string) {
97+
return callback ? callback(this.value) : `${this.value}`;
98+
}
99+
}

src/data-structures/Graph/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './Graph';
2+
export * from './GraphEdge';
3+
export * from './GraphVertex';

0 commit comments

Comments
 (0)