Skip to content

Commit

Permalink
-update
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoonz committed Feb 24, 2022
1 parent b24ddf5 commit 0539c05
Show file tree
Hide file tree
Showing 210 changed files with 27,214 additions and 4,159 deletions.
1 change: 1 addition & 0 deletions docs/content/ds/data-structures/graphs/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include::../../../book/content/part03/graph.asc[]
52 changes: 52 additions & 0 deletions docs/content/ds/data-structures/graphs/graph-scale.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const Graph = require('./graph');

xdescribe('Graph (social network)', () => {
let graph;

beforeEach(() => {
graph = new Graph(Graph.UNDIRECTED);

graph.addEdge('You', 'James');
graph.addEdge('James', 'Michael');
graph.addEdge('James', 'William');
graph.addEdge('You', 'John');
graph.addEdge('John', 'Linda');
graph.addEdge('Linda', 'Elizabeth');
graph.addEdge('You', 'Robert');
graph.addEdge('You', 'Mary');
graph.addEdge('You', 'Patricia');
graph.addEdge('You', 'Jennifer');
graph.addEdge('You', 'Larry');
graph.addEdge('You', 'Eric');
graph.addEdge('David', 'Barbara');
graph.addEdge('Richard', 'Susan');
graph.addEdge('Joseph', 'Jessica');
graph.addEdge('Susan', 'Joseph');
graph.addEdge('Mark', 'Jan');
graph.addEdge('Mark', 'David');
graph.addEdge('Mark', 'Dustin');
graph.addEdge('Mark', 'Owen');
graph.addEdge('Mark', 'Pricilla');
graph.addEdge('Mark', 'Andrew');
graph.addEdge('Mark', 'Adam');
graph.addEdge('Pricilla', 'Richard');
graph.addEdge('Jessica', 'Elizabeth');
graph.addEdge('Mary', 'Barbara');
graph.addEdge('William', 'Jan');
graph.addEdge('Joseph', 'Robert');
graph.addEdge('Dustin', 'Michael');
graph.addEdge('Andrew', 'William');
graph.addEdge('Jessica', 'John');
graph.addEdge('Adam', 'Susan');
graph.addEdge('William', 'Barbara');
graph.addEdge('Joseph', 'Patricia');
graph.addEdge('Joseph', 'Michael');
graph.addEdge('Elizabeth', 'Adam');
graph.addEdge('Jan', 'Elizabeth');
graph.addEdge('Richard', 'Joseph');
});

it('should return all paths connecting you and mark', () => {
expect(graph.findPath('You', 'Mark')).toEqual([]);
});
});
258 changes: 258 additions & 0 deletions docs/content/ds/data-structures/graphs/graph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// npx eslint --fix -f codeframe lib/data-structures/graphs/graph.js
const Node = require('./node');
const Stack = require('../stacks/stack');
const Queue = require('../queues/queue');
const HashMap = require('../maps/hash-maps/hash-map');

// tag::constructor[]
/**
* Graph data structure implemented with an adjacent list
*/
class Graph {
/**
* Initialize the nodes map
* @param {Symbol} edgeDirection either `Graph.DIRECTED` or `Graph.UNDIRECTED`
*/
constructor(edgeDirection = Graph.DIRECTED) {
this.nodes = new HashMap();
this.edgeDirection = edgeDirection;
}
// end::constructor[]

// tag::addVertex[]
/**
* Add a node to the graph.
* Runtime: O(1)
* @param {any} value node's value
* @returns {Node} the new node or the existing one if it already exits.
*/
addVertex(value) {
if (this.nodes.has(value)) { // <1>
return this.nodes.get(value);
}
const vertex = new Node(value); // <2>
this.nodes.set(value, vertex); // <3>
return vertex;
}
// end::addVertex[]

// tag::removeVertex[]
/**
* Removes node from graph
* It also removes the reference of the deleted node from
* anywhere it was adjacent to.
* Runtime: O(|V|) because adjacency list is implemented with a HashSet.
* It were implemented with an array then it would be O(|V| + |E|).
* @param {any} value node's value
*/
removeVertex(value) {
const current = this.nodes.get(value); // <1>
if (current) {
Array.from(this.nodes.values()).forEach((node) => node.removeAdjacent(current)); // <2>
}
return this.nodes.delete(value); // <3>
}
// end::removeVertex[]

// tag::addEdge[]
/**
* Create a connection between the source node and the destination node.
* If the graph is undirected, it will also create the link from destination to source.
* If the nodes don't exist, then it will make them on the fly.
* Runtime: O(1)
* @param {any} source
* @param {any} destination
* @returns {[Node, Node]} source/destination node pair
*/
addEdge(source, destination) {
const sourceNode = this.addVertex(source); // <1>
const destinationNode = this.addVertex(destination); // <1>

sourceNode.addAdjacent(destinationNode); // <2>

if (this.edgeDirection === Graph.UNDIRECTED) {
destinationNode.addAdjacent(sourceNode); // <3>
}

return [sourceNode, destinationNode];
}
// end::addEdge[]

// tag::removeEdge[]
/**
* Remove the connection between source node and destination.
* If the graph is undirected, it will also create the link from destination to source.
*
* Runtime: O(1): implemented with HashSet.
* If implemented with array, would be O(|E|).
*
* @param {any} source
* @param {any} destination
*/
removeEdge(source, destination) {
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);

if (sourceNode && destinationNode) {
sourceNode.removeAdjacent(destinationNode);

if (this.edgeDirection === Graph.UNDIRECTED) {
destinationNode.removeAdjacent(sourceNode);
}
}

return [sourceNode, destinationNode];
}
// end::removeEdge[]

// tag::areAdjacents[]
/**
* True if two nodes are adjacent.
* @param {any} source node's value
* @param {any} destination node's value
*/
areAdjacents(source, destination) {
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);

if (sourceNode && destinationNode) {
return sourceNode.isAdjacent(destinationNode);
}

return false;
}
// end::areAdjacents[]

// tag::graphSearch[]
/**
* Depth-first search
* Use a stack to visit nodes (LIFO)
* @param {Node} first node to start the dfs
*/
static* dfs(first) {
yield* Graph.graphSearch(first, Stack);
}

/**
* Breadth-first search
* Use a queue to visit nodes (FIFO)
* @param {Node} first node to start the dfs
*/
static* bfs(first) {
yield* Graph.graphSearch(first, Queue);
}

/**
* Generic graph search where we can pass a Stack or Queue
* @param {Node} first node to start the search
* @param {Stack|Queue} Type Stack for DFS or Queue for BFS
*/
static* graphSearch(first, Type = Stack) {
const visited = new Map();
const visitList = new Type();

visitList.add(first);

while (!visitList.isEmpty()) {
const node = visitList.remove();
if (node && !visited.has(node)) {
yield node;
visited.set(node);
node.getAdjacents().forEach((adj) => visitList.add(adj));
}
}
}
// end::graphSearch[]

/**
* Return true if two nodes are connected and false if not
* @param {any} source vertex's value
* @param {*} destination vertex's value
*/
areConnected(source, destination) {
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);

if (sourceNode && destinationNode) {
const bfsFromFirst = Graph.bfs(sourceNode);
// eslint-disable-next-line no-restricted-syntax
for (const node of bfsFromFirst) {
if (node === destinationNode) {
return true;
}
}
}

return false;
}

/**
* Find a path between source and destination
* It might not be the optimal path
*
* @param {any} source vertex's value
* @param {any} destination vertex's value
* @param {Map<Node>} newPath current path from source to destination
* @returns list of nodes from source to destination
*/
findPath(source, destination, path = new Map()) {
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);
const newPath = new Map(path);

if (!destinationNode || !sourceNode) return [];

newPath.set(sourceNode);

if (source === destination) {
return Array.from(newPath.keys());
}

// eslint-disable-next-line no-restricted-syntax
for (const node of sourceNode.getAdjacents()) {
if (!newPath.has(node)) {
const nextPath = this.findPath(node.value, destination, newPath);
if (nextPath.length) {
return nextPath;
}
}
}

return [];
}

/**
* Find all paths from source to destination
*
* @param {any} source vertex'value
* @param {any} destination vertex'value
* @param {Map} path (optional) used for recursion
*/
findAllPaths(source, destination, path = new Map()) {
const sourceNode = this.nodes.get(source);
const destinationNode = this.nodes.get(destination);
const newPath = new Map(path);

if (!destinationNode || !sourceNode) return [];

newPath.set(sourceNode);

if (source === destination) {
return [Array.from(newPath.keys())];
}

const paths = [];
sourceNode.getAdjacents().forEach((node) => {
if (!newPath.has(node)) {
const nextPaths = this.findAllPaths(node.value, destination, newPath);
nextPaths.forEach((nextPath) => paths.push(nextPath));
}
});
return paths;
}
}

Graph.UNDIRECTED = Symbol('undirected graph'); // two-way edges
Graph.DIRECTED = Symbol('directed graph'); // one-way edges

module.exports = Graph;
Loading

0 comments on commit 0539c05

Please sign in to comment.