/* * @author Samuel Neff (https://github.com/samuelneff) * * based almost entirely on gist from * * @author SHIN Suzuki (shinout310@gmail.com) * * https://gist.github.com/shinout/1232505 */ /// class EdgeNode { public afters:T[] = []; constructor(public id:T) {} } function sortDesc(a, b) { if (ab) return -1; // a must be equal to b return 0; } /** * general topological sort * @param edges : list of edges. each edge forms Array e.g. [12 , 3] * @param options When provided with 'continueOnCircularDependency' set to true, sorting will continue even if a * circular dependency is found. The precise sort is not guaranteed. * @returns Array : topological sorted list of IDs **/ function topsort(edges:T[][], options?:{continueOnCircularDependency: boolean}):T[] { var nodes: {[key:string]: EdgeNode } = {}; options = options || {continueOnCircularDependency: false}; var sorted:T[] = []; // hash: id of already visited node => true var visited: {[key: string]: boolean } = {}; // 1. build data structures edges.forEach(function(edge:T[]) { var fromEdge:T = edge[0]; var fromStr:string = fromEdge.toString(); var fromNode:EdgeNode; if (!(fromNode = nodes[fromStr])) { fromNode = nodes[fromStr] = new EdgeNode(fromEdge); } edge.forEach(function(toEdge:T) { // since from and to are in same array, we'll always see from again, so make sure we skip it.. if (toEdge == fromEdge) { return; } var toEdgeStr:string = toEdge.toString(); if (!nodes[toEdgeStr]) { nodes[toEdgeStr] = new EdgeNode(toEdge); } fromNode.afters.push(toEdge); }); }); // 2. topological sort var keys:string[] = Object.keys(nodes); keys.sort(sortDesc); keys.forEach(function visit(idstr:string, ancestorsIn:any) { var node:EdgeNode = nodes[idstr]; var id:T = node.id; // if already exists, do nothing if (visited[idstr]) { return; } var ancestors:T[] = Array.isArray(ancestorsIn) ? ancestorsIn : []; ancestors.push(id); visited[idstr] = true; node.afters.sort(sortDesc); node.afters.forEach(function(afterID:T) { // if already in ancestors, a closed chain exists. if (ancestors.indexOf(afterID) >= 0) { if (options.continueOnCircularDependency) { return; } throw new Error('Circular chain found: ' + id + ' must be before ' + afterID + ' due to a direct order specification, but ' + afterID + ' must be before ' + id + ' based on other specifications.'); } // recursive call visit(afterID.toString(), ancestors.map(function(v) { return v; })); }); sorted.unshift(id); }); return sorted; } export = topsort;