ember-material-icons
Version:
Google Material icons for your ember-cli app
177 lines (166 loc) • 4.05 kB
JavaScript
;
function visit(vertex, fn, visited, path) {
let name = vertex.name,
vertices = vertex.incoming,
names = vertex.incomingNames,
len = names.length,
i;
if (!visited) {
visited = {};
}
if (!path) {
path = [];
}
if (visited.hasOwnProperty(name)) {
return;
}
path.push(name);
visited[name] = true;
for (i = 0; i < len; i++) {
visit(vertices[names[i]], fn, visited, path);
}
fn(vertex, path);
path.pop();
}
class DAG {
/**
* Directed acyclic graph.
*
* see [Wikipedia](https://en.wikipedia.org/wiki/Directed_acyclic_graph).
*
* @class DAG
* @constructor
*/
constructor() {
/**
* Array of all known vertex names.
*
* @final
* @property names
* @type Array
*/
this.names = [];
/**
* Hash of vertices keyed by name.
*
* @final
* @property vertices
* @type Object
*/
this.vertices = {};
}
/**
* Adds a new vertex to the graph.
*
* @method add
* @param {String} name Name of the vertex
* @return {Object|undefined} The new vertex or the already existing vertex with the same name
*/
add(name) {
if (!name) { return; }
if (this.vertices.hasOwnProperty(name)) {
return this.vertices[name];
}
let vertex = {
name,
incoming: {},
incomingNames: [],
hasOutgoing: false,
value: null,
};
this.vertices[name] = vertex;
this.names.push(name);
return vertex;
}
/**
* Assigns `value` to the vertex named `name`.
*
* Creates a new vertex if a vertex named `name` does not exist yet.
*
* @method map
* @param {String} name Name of the vertex
* @param {any} value Value of the vertex
*/
map(name, value) {
this.add(name).value = value;
}
/**
* Adds an edge between two vertices.
*
* @method addEdge
* @param fromName Name of the `from` vertex
* @param toName Name of the `to` vertex
* @throws {Error} if a cycle is detected
*/
addEdge(fromName, toName) {
if (!fromName || !toName || fromName === toName) {
return;
}
let from = this.add(fromName), to = this.add(toName);
if (to.incoming.hasOwnProperty(fromName)) {
return;
}
function checkCycle(vertex, path) {
if (vertex.name === toName) {
throw new Error(`cycle detected: ${toName} <- ${path.join(' <- ')}`);
}
}
visit(from, checkCycle);
from.hasOutgoing = true;
to.incoming[fromName] = from;
to.incomingNames.push(fromName);
}
/**
* Traverses the graph depth-first.
*
* Each vertex will only be visited once even if it has multiple parent vertices.
*
* @method topsort
* @param {Function} fn Function that is called with each vertex and the path to it
*/
topsort(fn) {
let visited = {},
vertices = this.vertices,
names = this.names,
len = names.length,
i, vertex;
for (i = 0; i < len; i++) {
vertex = vertices[names[i]];
if (!vertex.hasOutgoing) {
visit(vertex, fn, visited);
}
}
}
/**
* Assigns `value` to the vertex named `name` and creates the edges defined by `before` and `after`.
*
* @method addEdges
* @param {String} name Name of the vertex
* @param {any} value Value of the vertex
* @param {String|Array} before Adds edges from `name` to each of `before`
* @param {String|Array} after Adds edges from each of `after` to `name`
*/
addEdges(name, value, before, after) {
let i;
this.map(name, value);
if (before) {
if (typeof before === 'string') {
this.addEdge(name, before);
} else {
for (i = 0; i < before.length; i++) {
this.addEdge(name, before[i]);
}
}
}
if (after) {
if (typeof after === 'string') {
this.addEdge(after, name);
} else {
for (i = 0; i < after.length; i++) {
this.addEdge(after[i], name);
}
}
}
}
}
module.exports = DAG;