diff --git a/packages/event/__tests__/dag.test.ts b/packages/event/__tests__/dag.test.ts new file mode 100644 index 00000000..0ba32058 --- /dev/null +++ b/packages/event/__tests__/dag.test.ts @@ -0,0 +1,21 @@ +import DAG from '../src/graph'; + +test('dag', () => { + const dag = new DAG({ + nodes: [1, 2, 3, 4, 5, 6], + links: [ + { parent_id: 1, child_id: 2 }, + { parent_id: 2, child_id: 3 }, + { parent_id: 2, child_id: 4 }, + { parent_id: 3, child_id: 1 }, + { parent_id: 4, child_id: 5 } + ], + link_key: { + start: 'parent_id', + end: 'child_id' + } + }); + + console.log(dag.hasCycle()); + console.log(dag.findParents(5)); +}); diff --git a/packages/event/package.json b/packages/event/package.json index 15243016..258912e3 100644 --- a/packages/event/package.json +++ b/packages/event/package.json @@ -1,6 +1,6 @@ { "name": "@dpdfe/event-utils", - "version": "0.0.25", + "version": "0.0.26", "description": "通用方法", "author": "pearone", "homepage": "https://github.com/DPDFE/react-layout/tree/main/packages/event", @@ -24,7 +24,7 @@ "dev": "tsc -w", "build": "rm -rf dist && tsc --build", "test": "jest", - "test:single": "jest ./__tests__/autoindex.test.ts", + "test:single": "jest ./__tests__/dag.test.ts", "prepublishOnly": "npm run build" }, "bugs": { diff --git a/packages/event/src/graph.ts b/packages/event/src/graph.ts new file mode 100644 index 00000000..72ad1d1e --- /dev/null +++ b/packages/event/src/graph.ts @@ -0,0 +1,238 @@ +/** Link规则 */ +interface OriginLink { + [key: string]: OriginGraphNodeType; +} + +/** 所有Link key */ +type OriginLinkKeys = keyof OriginLink; + +/** 映射key */ +interface OriginKey { + start: OriginLinkKeys; + end: OriginLinkKeys; +} + +/** 节点类型 */ +type OriginGraphNodeType = number | string; + +const DEFAULT_START = 'start'; +const DEFAULT_END = 'end'; + +/** 有向无环图 */ +class DAG { + /** 所有节点 */ + nodes: Set = new Set(); + + /** 所有边child关系 */ + children: Map> = new Map(); + + /** 所有边parents关系 */ + parents: Map> = new Map(); + + /** 创建一个图 */ + constructor({ + nodes, + links, + link_key + }: { + nodes: OriginGraphNodeType[]; + links: OriginLink[]; + link_key: OriginKey; + }) { + this.init(nodes, links, link_key); + } + + init( + nodes: OriginGraphNodeType[], + links: OriginLink[], + link_key: OriginKey + ) { + this.fillNodes(nodes); + this.fillLinks(links, link_key); + } + + /** 填充node */ + fillNodes(nodes: OriginGraphNodeType[]) { + this.nodes = new Set(nodes); + + const iterator = this.nodes.values(); + let result = iterator.next(); + while (!result.done) { + this.addNode(result.value); + result = iterator.next(); + } + } + + /** 填充link */ + fillLinks( + links: OriginLink[], + link_key: OriginKey = { start: DEFAULT_START, end: DEFAULT_END } + ) { + links.map((link) => { + const start_key = link[link_key.start]; + const end_key = link[link_key.end]; + this.addLink(start_key, end_key); + }); + } + + /** 删除节点 */ + removeNode(node: OriginGraphNodeType) { + this.nodes.delete(node); + this.children.delete(node); + this.parents.delete(node); + } + + /** 添加节点 */ + addNode(node: OriginGraphNodeType) { + this.nodes.add(node); + if (!this.children.get(node)) { + this.children.set(node, new Set()); + } + if (!this.parents.get(node)) { + this.parents.set(node, new Set()); + } + } + + /** 添加一条边 */ + addLink(start: OriginGraphNodeType, end: OriginGraphNodeType) { + this.addLinkChild(start, end); + this.addLinkParent(start, end); + } + + /** 添加一条边 */ + addLinkChild(start: OriginGraphNodeType, end: OriginGraphNodeType) { + const target = this.children.get(start); + if (target && this.nodes.has(end)) { + target.add(end); + } else { + console.error(`有未知节点: ${start} 或 ${end}`); + } + } + + /** 添加一条边 */ + addLinkParent(start: OriginGraphNodeType, end: OriginGraphNodeType) { + const target = this.parents.get(end); + if (target && this.nodes.has(start)) { + target.add(start); + } else { + console.error(`有未知节点: ${start} 或 ${end}`); + } + } + + /** 删除一条边 */ + removeLink(start: OriginGraphNodeType, end: OriginGraphNodeType) { + this.removeLinkChild(start, end); + this.removeLinkParent(start, end); + } + + /** 删除一条边 */ + removeLinkChild(start: OriginGraphNodeType, end: OriginGraphNodeType) { + const target = this.children.get(start); + if (target && this.nodes.has(end)) { + target.delete(end); + } else { + console.error(`有未知节点: ${start} 或 ${end}`); + } + } + + /** 删除一条边 */ + removeLinkParent(start: OriginGraphNodeType, end: OriginGraphNodeType) { + const target = this.parents.get(end); + if (target && this.nodes.has(start)) { + target.delete(start); + } else { + console.error(`有未知节点: ${start} 或 ${end}`); + } + } + + /** 清空 */ + clear() { + this.nodes = new Set(); + this.children = new Map(); + this.parents = new Map(); + } + + /** 验证全图成环 */ + hasCycle() { + const visited: Set = new Set(); + const stack: Set = new Set(); + + for (const node of this.nodes.values()) { + if (this.detectCycle(node, visited, stack)) { + return true; + } + } + return false; + } + + /** 验证单图环 */ + detectCycle( + node: OriginGraphNodeType, + visited: Set, + stack: Set + ) { + if (stack.has(node)) { + // 如果当前节点已经在递归栈中,则存在环 + return true; + } + if (visited.has(node)) { + // 如果当前节点已经访问过,则不需要再继续探索 + return false; + } + + visited.add(node); + stack.add(node); + + const children = this.children.get(node)!; + + for (const child of children.values()) { + if (this.detectCycle(child, visited, stack)) { + return true; + } + } + + stack.delete(node); + return false; + } + + /** 找到所有的父节点 */ + findParents(node: OriginGraphNodeType) { + const parents: Set = new Set(); + const stack: Set = new Set(); + + this.detectParent(node, parents, stack); + return parents; + } + + /** 获取父元素 */ + detectParent( + node: OriginGraphNodeType, + visited: Set, + stack: Set + ) { + if (stack.has(node)) { + // 如果当前节点已经在递归栈中 + return; + } + if (visited.has(node)) { + // 如果当前节点已经访问过,则不需要再继续探索 + return; + } + + visited.add(node); + stack.add(node); + + const parents = this.parents.get(node)!; + + for (const parent of parents.values()) { + if (this.detectParent(parent, visited, stack)) { + return true; + } + } + + stack.delete(node); + return; + } +} + +export default DAG; diff --git a/packages/event/src/index.ts b/packages/event/src/index.ts index 27df363f..e2b37671 100644 --- a/packages/event/src/index.ts +++ b/packages/event/src/index.ts @@ -40,3 +40,7 @@ export { gray, textColor, opacity } from './color/calc'; export { darken, lighten, range } from './color/computed'; export { log } from './log'; + +import DAG from './graph'; + +export { DAG };