76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import {type Edge, type Node} from '@xyflow/react'
|
|
|
|
export const buildEL = (nodes: Node[], edges: Edge[]): string => {
|
|
const nodeMap: Map<string, Node> = new Map<string, Node>()
|
|
// 构建邻接列表和内图
|
|
const adjList = new Map<string, string[]>()
|
|
const inDegree = new Map<string, number>()
|
|
for (const node of nodes) {
|
|
nodeMap.set(node.id, node)
|
|
adjList.set(node.id, [])
|
|
inDegree.set(node.id, 0)
|
|
}
|
|
for (const edge of edges) {
|
|
adjList.get(edge.source)!.push(edge.target)
|
|
inDegree.set(edge.target, inDegree.get(edge.target)! + 1)
|
|
}
|
|
|
|
// Compute levels (longest path from start)
|
|
const levelMap = new Map<string, number>()
|
|
|
|
function computeLevel(nodeId: string): number {
|
|
if (levelMap.has(nodeId)) return levelMap.get(nodeId)!
|
|
const preds = edges.filter(e => e.target === nodeId).map(e => e.source)
|
|
const level = preds.length === 0 ? 0 : Math.max(...preds.map(p => computeLevel(p))) + 1
|
|
levelMap.set(nodeId, level)
|
|
return level
|
|
}
|
|
|
|
for (const node of nodes) computeLevel(node.id)
|
|
|
|
// Group nodes by level
|
|
const maxLevel = Math.max(...Array.from(levelMap.values()))
|
|
const levels: string[][] = Array.from({length: maxLevel + 1}, () => [])
|
|
for (const node of nodes) levels[levelMap.get(node.id)!].push(node.id)
|
|
|
|
const covertNodeFromId = (id: string) => {
|
|
let node = nodeMap.get(id)!
|
|
return `node("${node.type}").bind("nodeId", ${node.id})`
|
|
}
|
|
|
|
// Build EL expression
|
|
const expressions: string[] = []
|
|
for (let i = 0; i <= maxLevel; i++) {
|
|
const nodesAtLevel = levels[i]
|
|
if (nodesAtLevel.length === 0) continue
|
|
|
|
// 识别从这个级别开始的串行链
|
|
const serialChains: string[] = []
|
|
for (const nodeId of nodesAtLevel) {
|
|
let chain = [nodeId]
|
|
let current = nodeId
|
|
while (adjList.get(current)?.length === 1) {
|
|
const next = adjList.get(current)![0]
|
|
if (inDegree.get(next) === 1 && levelMap.get(next) === i + chain.length) {
|
|
chain.push(next)
|
|
current = next
|
|
} else break
|
|
}
|
|
if (chain.length > 1) {
|
|
serialChains.push(`THEN(${chain.map(id => covertNodeFromId(id)).join(',')})`)
|
|
// Remove processed nodes from their levels
|
|
for (let j = 1; j < chain.length; j++) {
|
|
const level = levelMap.get(chain[j])!
|
|
levels[level] = levels[level].filter(n => n !== chain[j])
|
|
}
|
|
} else {
|
|
serialChains.push(covertNodeFromId(nodeId))
|
|
}
|
|
}
|
|
|
|
// Combine chains or nodes at this level
|
|
expressions.push(serialChains.length > 1 ? `WHEN(${serialChains.join(', ')})` : serialChains[0])
|
|
}
|
|
|
|
return `THEN(${expressions.join(',')})`
|
|
} |