makit
Version:
Make in JavaScript done right!
172 lines (171 loc) • 5.08 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DirectedGraph = void 0;
const treeify_1 = require("treeify");
const inspect = Symbol.for('nodejs.util.inspect.custom') || 'inspect';
var VertexType;
(function (VertexType) {
VertexType[VertexType["None"] = 0] = "None";
VertexType[VertexType["In"] = 1] = "In";
VertexType[VertexType["Out"] = 2] = "Out";
VertexType[VertexType["InOut"] = 3] = "InOut";
})(VertexType || (VertexType = {}));
class DirectedGraph {
constructor(vertexToString = x => String(x) + '') {
this.vertexToString = vertexToString;
this.edges = new Map();
this.redges = new Map();
this.vertices = new Map();
}
/**
* 增加一个点
*
* @param v 要增加的点
* @param vertexType 点的类型(空、入、出、出入)
*/
addVertex(v, vertexType = VertexType.None) {
const type = this.vertices.get(v) || VertexType.None;
this.vertices.set(v, type | vertexType);
if (!this.root)
this.root = v;
}
/**
* 增加一条边
*
* @param fr 边的起点
* @param to 边的终点
*/
addEdge(fr, to) {
if (!this.edges.has(fr)) {
this.edges.set(fr, new Set());
}
this.edges.get(fr).add(to);
if (!this.redges.has(to)) {
this.redges.set(to, new Set());
}
this.redges.get(to).add(fr);
this.addVertex(fr, VertexType.Out);
this.addVertex(to, VertexType.In);
}
/**
* 检查是否包含边
*
* @param fr 起点
* @param to 终点
* @return 包含返回 true 否则 false
*/
hasEdge(fr, to) {
if (!this.edges.has(fr))
return false;
return this.edges.get(fr).has(to);
}
*getOutVerticies(u) {
for (const v of this.edges.get(u) || [])
yield v;
}
*getInVertices(u) {
for (const v of this.redges.get(u) || [])
yield v;
}
getOutDegree(u) {
return this.edges.has(u) ? this.edges.get(u).size : 0;
}
/**
* 是否存在环状结构
*
* @return 如果存在返回一个 circuit,否则返回 null
*/
checkCircular(u, path = new Set(), visited = new Set()) {
if (path.has(u)) {
let patharr = [...path];
patharr = [...patharr.slice(patharr.indexOf(u)), u];
throw new Error(`Circular detected: ${patharr.join(' -> ')}`);
}
if (visited.has(u))
return;
else
visited.add(u);
path.add(u);
for (const v of this.getOutVerticies(u)) {
this.checkCircular(v, path, visited);
}
path.delete(u);
}
/**
* 获取一条从 vertex 到 root 的路径
*
* @param vertex 路径的起点
* @return 从 vertex 到 root 的路径
*/
findPathToRoot(vertex) {
const seen = new Set();
while (true) {
// 出现循环引用时,数组收尾相同
if (seen.has(vertex))
return [...seen, vertex];
else
seen.add(vertex);
const parents = this.redges.get(vertex);
if (!parents || !parents.size)
break;
vertex = parents.values().next().value;
}
return [...seen];
}
[inspect]() {
return this.toString();
}
getInVerticesRecursively(target) {
const dependants = new Set();
const queue = [target];
for (const node of queue) {
if (dependants.has(node))
continue;
dependants.add(node);
for (const parent of this.redges.get(node) || []) {
queue.push(parent);
}
}
return dependants;
}
/**
* 以第一个点为根的树的文本表示
*
* @return 树的 ASCII 文本表示
*/
toString() {
if (!this.root)
return '[Empty Tree]';
const root = this.vertexToString(this.root);
const tree = treeify_1.asTree(this.toTree(), false, false);
return `${root}\n${tree}`;
}
/**
* 转化为 Plain Object 表示的树,用于 treeify
*
* 注意:使用前需要先调用 checkCircular(),
* 或从数据上确保它是一棵树。
*
* @return 转为 Plain Object 的树状结构
*/
toTree(root = this.root) {
const tree = {};
if (!root)
throw new Error('root not found');
for (const child of this.getOutVerticies(root)) {
tree[this.vertexToString(child)] = this.toTree(child);
}
return tree;
}
*preOrder(vertex, visited = new Set()) {
if (visited.has(vertex))
return;
else
visited.add(vertex);
yield vertex;
for (const child of this.getOutVerticies(vertex)) {
yield* this.preOrder(child, visited);
}
}
}
exports.DirectedGraph = DirectedGraph;