Add graph.

This commit is contained in:
Oleksii Trekhleb 2018-04-10 11:42:32 +03:00
parent 840635e613
commit 67cdad8030
8 changed files with 426 additions and 1 deletions

View File

@ -8,6 +8,7 @@
"rules": {
"no-bitwise": "off",
"no-lonely-if": "off",
"class-methods-use-this": "off"
"class-methods-use-this": "off",
"arrow-body-style": "off"
}
}

View File

@ -15,6 +15,7 @@
8. [Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree)
* [Binary Search Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/binary-search-tree)
* [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree)
9. [Graph](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/graph)
## [Algorithms](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms)

View File

@ -0,0 +1,88 @@
export default class Graph {
/**
* @param isDirected {boolean}
*/
constructor(isDirected = false) {
this.vertices = {};
this.isDirected = isDirected;
}
/**
* @param newVertex {GraphVertex}
* @returns {Graph}
*/
addVertex(newVertex) {
this.vertices[newVertex.getKey()] = newVertex;
return this;
}
/**
* @param vertexKey {string}
* @returns GraphVertex
*/
getVertexByKey(vertexKey) {
return this.vertices[vertexKey];
}
/**
* @param edge {GraphEdge}
* @returns {Graph}
*/
addEdge(edge) {
// Try to find and end start vertices.
let startVertex = this.getVertexByKey(edge.startVertex.getKey());
let endVertex = this.getVertexByKey(edge.endVertex.getKey());
// Insert start vertex if it wasn't inserted.
if (!startVertex) {
this.addVertex(edge.startVertex);
startVertex = this.getVertexByKey(edge.startVertex.getKey());
}
// Insert end vertex if it wasn't inserted.
if (!endVertex) {
this.addVertex(edge.endVertex);
endVertex = this.getVertexByKey(edge.endVertex.getKey());
}
// @TODO: Check if edge has been already added.
// Add edge to the vertices.
if (this.isDirected) {
// If graph IS directed then add the edge only to start vertex.
startVertex.addEdge(edge);
} else {
// If graph ISN'T directed then add the edge to both vertices.
startVertex.addEdge(edge);
endVertex.addEdge(edge);
}
return this;
}
/**
* @param startVertex {GraphVertex}
* @param endVertex {GraphVertex}
*/
findEdge(startVertex, endVertex) {
const vertex = this.getVertexByKey(startVertex.getKey());
return vertex.findEdge(endVertex);
}
/**
* @param vertexKey {string}
* @returns {GraphVertex}
*/
findVertexByKey(vertexKey) {
if (this.vertices[vertexKey]) {
return this.vertices[vertexKey];
}
return null;
}
toString() {
return Object.keys(this.vertices).toString();
}
}

View File

@ -0,0 +1,12 @@
export default class GraphEdge {
/**
* @param startVertex {GraphVertex}
* @param endVertex {GraphVertex}
* @param weight {number}
*/
constructor(startVertex, endVertex, weight = 1) {
this.startVertex = startVertex;
this.endVertex = endVertex;
this.weight = weight;
}
}

View File

@ -0,0 +1,85 @@
import LinkedList from '../linked-list/LinkedList';
export default class GraphVertex {
constructor(value) {
if (value === undefined) {
throw new Error('Graph vertex must have a value');
}
// Normally you would store string value like vertex name.
// But generally it may be any object as well
this.value = value;
this.edges = new LinkedList();
}
/**
* @param edge {GraphEdge}
* @returns {GraphVertex}
*/
addEdge(edge) {
this.edges.append(edge);
return this;
}
getNeighbors() {
const edges = this.edges.toArray();
const neighborsConverter = ({ value }) => {
return value.startVertex === this ? value.endVertex : value.startVertex;
};
// Return either start or end vertex.
// For undirected graphs it is possible that current vertex will be the end one.
return edges.map(neighborsConverter);
}
/**
* @param requiredEdge {GraphEdge}
* @returns {boolean}
*/
hasEdge(requiredEdge) {
const edgeNode = this.edges.find({
callback: edge => edge === requiredEdge,
});
return !!edgeNode;
}
/**
* @param vertex {GraphVertex}
* @returns {boolean}
*/
hasNeighbor(vertex) {
const vertexNode = this.edges.find({
callback: edge => edge.startVertex === vertex || edge.endVertex === vertex,
});
return !!vertexNode;
}
findEdge(vertex) {
const edgeFinder = (edge) => {
return edge.startVertex === vertex || edge.endVertex === vertex;
};
const edge = this.edges.find({ callback: edgeFinder });
return edge ? edge.value : null;
}
/**
* @returns {string}
*/
getKey() {
return this.value;
}
/**
* @param callback {function}
* @returns {string}
*/
toString(callback) {
return callback ? callback(this.value) : `${this.value}`;
}
}

View File

@ -0,0 +1,114 @@
import Graph from '../Graph';
import GraphVertex from '../GraphVertex';
import GraphEdge from '../GraphEdge';
describe('Graph', () => {
it('should add vertices to graph', () => {
const graph = new Graph();
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
graph
.addVertex(vertexA)
.addVertex(vertexB);
expect(graph.toString()).toBe('A,B');
expect(graph.getVertexByKey(vertexA.getKey())).toEqual(vertexA);
expect(graph.getVertexByKey(vertexB.getKey())).toEqual(vertexB);
});
it('should add edges to undirected graph', () => {
const graph = new Graph();
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const edgeAB = new GraphEdge(vertexA, vertexB);
graph.addEdge(edgeAB);
const graphVertexA = graph.findVertexByKey(vertexA.getKey());
const graphVertexB = graph.findVertexByKey(vertexB.getKey());
expect(graph.toString()).toBe('A,B');
expect(graphVertexA).toBeDefined();
expect(graphVertexB).toBeDefined();
expect(graph.findVertexByKey('not existing')).toBeNull();
expect(graphVertexA.getNeighbors().length).toBe(1);
expect(graphVertexA.getNeighbors()[0]).toEqual(vertexB);
expect(graphVertexA.getNeighbors()[0]).toEqual(graphVertexB);
expect(graphVertexB.getNeighbors().length).toBe(1);
expect(graphVertexB.getNeighbors()[0]).toEqual(vertexA);
expect(graphVertexB.getNeighbors()[0]).toEqual(graphVertexA);
});
it('should add edges to directed graph', () => {
const graph = new Graph(true);
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const edgeAB = new GraphEdge(vertexA, vertexB);
graph.addEdge(edgeAB);
const graphVertexA = graph.findVertexByKey(vertexA.getKey());
const graphVertexB = graph.findVertexByKey(vertexB.getKey());
expect(graph.toString()).toBe('A,B');
expect(graphVertexA).toBeDefined();
expect(graphVertexB).toBeDefined();
expect(graphVertexA.getNeighbors().length).toBe(1);
expect(graphVertexA.getNeighbors()[0]).toEqual(vertexB);
expect(graphVertexA.getNeighbors()[0]).toEqual(graphVertexB);
expect(graphVertexB.getNeighbors().length).toBe(0);
});
it('should find edge by vertices in undirected graph', () => {
const graph = new Graph();
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const edgeAB = new GraphEdge(vertexA, vertexB, 10);
graph.addEdge(edgeAB);
const graphEdgeAB = graph.findEdge(vertexA, vertexB);
const graphEdgeBA = graph.findEdge(vertexB, vertexA);
const graphEdgeAC = graph.findEdge(vertexB, vertexC);
expect(graphEdgeAC).toBeNull();
expect(graphEdgeAB).toEqual(edgeAB);
expect(graphEdgeBA).toEqual(edgeAB);
expect(graphEdgeAB.weight).toBe(10);
});
it('should find edge by vertices in directed graph', () => {
const graph = new Graph(true);
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const edgeAB = new GraphEdge(vertexA, vertexB, 10);
graph.addEdge(edgeAB);
const graphEdgeAB = graph.findEdge(vertexA, vertexB);
const graphEdgeBA = graph.findEdge(vertexB, vertexA);
const graphEdgeAC = graph.findEdge(vertexB, vertexC);
expect(graphEdgeAC).toBeNull();
expect(graphEdgeBA).toBeNull();
expect(graphEdgeAB).toEqual(edgeAB);
expect(graphEdgeAB.weight).toBe(10);
});
});

View File

@ -0,0 +1,24 @@
import GraphEdge from '../GraphEdge';
import GraphVertex from '../GraphVertex';
describe('GraphEdge', () => {
it('should create graph edge with default weight', () => {
const startVertex = new GraphVertex('A');
const endVertex = new GraphVertex('B');
const edge = new GraphEdge(startVertex, endVertex);
expect(edge.startVertex).toEqual(startVertex);
expect(edge.endVertex).toEqual(endVertex);
expect(edge.weight).toEqual(1);
});
it('should create graph edge with predefined weight', () => {
const startVertex = new GraphVertex('A');
const endVertex = new GraphVertex('B');
const edge = new GraphEdge(startVertex, endVertex, 10);
expect(edge.startVertex).toEqual(startVertex);
expect(edge.endVertex).toEqual(endVertex);
expect(edge.weight).toEqual(10);
});
});

View File

@ -0,0 +1,100 @@
import GraphVertex from '../GraphVertex';
import GraphEdge from '../GraphEdge';
describe('GraphVertex', () => {
it('should throw an error when trying to create vertex without value', () => {
let vertex = null;
function createEmptyVertex() {
vertex = new GraphVertex();
}
expect(vertex).toBeNull();
expect(createEmptyVertex).toThrow();
});
it('should create graph vertex', () => {
const vertex = new GraphVertex('A');
expect(vertex).toBeDefined();
expect(vertex.value).toBe('A');
expect(vertex.toString()).toBe('A');
expect(vertex.getKey()).toBe('A');
expect(vertex.edges.toString()).toBe('');
});
it('should add edges to vertex and check if it exists', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('A');
const edgeAB = new GraphEdge(vertexA, vertexB);
vertexA.addEdge(edgeAB);
expect(vertexA.hasEdge(edgeAB)).toBeTruthy();
expect(vertexB.hasEdge(edgeAB)).toBeFalsy();
});
it('should return vertex neighbors in case if current node is start one', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const edgeAB = new GraphEdge(vertexA, vertexB);
const edgeAC = new GraphEdge(vertexA, vertexC);
vertexA
.addEdge(edgeAB)
.addEdge(edgeAC);
expect(vertexB.getNeighbors()).toEqual([]);
const neighbors = vertexA.getNeighbors();
expect(neighbors.length).toBe(2);
expect(neighbors[0]).toEqual(vertexB);
expect(neighbors[1]).toEqual(vertexC);
});
it('should return vertex neighbors in case if current node is end one', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const edgeBA = new GraphEdge(vertexB, vertexA);
const edgeCA = new GraphEdge(vertexC, vertexA);
vertexA
.addEdge(edgeBA)
.addEdge(edgeCA);
expect(vertexB.getNeighbors()).toEqual([]);
const neighbors = vertexA.getNeighbors();
expect(neighbors.length).toBe(2);
expect(neighbors[0]).toEqual(vertexB);
expect(neighbors[1]).toEqual(vertexC);
});
it('should check if vertex has specific neighbor', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const edgeAB = new GraphEdge(vertexA, vertexB);
vertexA.addEdge(edgeAB);
expect(vertexA.hasNeighbor(vertexB)).toBeTruthy();
expect(vertexA.hasNeighbor(vertexC)).toBeFalsy();
});
it('should edge by vertex', () => {
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const edgeAB = new GraphEdge(vertexA, vertexB);
vertexA.addEdge(edgeAB);
expect(vertexA.findEdge(vertexB)).toEqual(edgeAB);
expect(vertexA.findEdge(vertexC)).toBeNull();
});
});