This repository has been archived by the owner on Jan 18, 2018. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: rework algo, now using topo walk after grouping
- Loading branch information
Showing
5 changed files
with
157 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
'use strict'; | ||
|
||
const hoi = require('ho-iter'); | ||
const series = hoi.series; | ||
|
||
const VertexSet = require('./vertex-set'); | ||
const CircularDependencyError = require('./circular-dependency-error'); | ||
|
||
module.exports = resolve; | ||
|
||
class TopoGroups { | ||
constructor() { | ||
this._groups = []; | ||
this._index = Object.create(null); | ||
} | ||
lookup(id) { | ||
return this._index[id]; | ||
} | ||
lookupCreate(id) { | ||
let group = this.lookup(id); | ||
if (!group) { | ||
group = new Set([id]); | ||
this._index[id] = group; | ||
this._groups.push(group); | ||
} | ||
return group; | ||
} | ||
merge(vertexId, parentId) { | ||
const parentGroup = this.lookupCreate(parentId); | ||
const vertexGroup = this.lookup(vertexId); | ||
|
||
if (parentGroup !== vertexGroup) { | ||
for (let id of vertexGroup) { | ||
this._index[id] = parentGroup; | ||
vertexGroup.delete(id); | ||
parentGroup.add(id); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function resolve(mixedGraph, startVertices, tech) { | ||
const _positions = startVertices.reduce((res, e, pos) => { res[e.id] = pos; return res; }, {}); | ||
const backsort = (a, b) => _positions[a.id] - _positions[b.id]; | ||
|
||
const orderedSuccessors = []; // L ← Empty list that will contain the sorted nodes | ||
const _orderedVisits = {}; // Hash with visiting flags: temporary - false, permanently - true | ||
const unorderedSuccessors = new VertexSet(); // The rest nodes | ||
let crumbs = []; | ||
const topo = new TopoGroups(); | ||
|
||
// ... while there are unmarked nodes do | ||
for (let v of startVertices) { | ||
visit(v, false); | ||
} | ||
|
||
const _orderedSuccessors = Array.from(new VertexSet(orderedSuccessors.reverse())); | ||
const _unorderedSuccessors = Array.from(unorderedSuccessors).sort(backsort); | ||
|
||
return series(_orderedSuccessors, _unorderedSuccessors); | ||
|
||
function visit(fromVertex, isWeak) { | ||
// ... if n has a temporary mark then stop (not a DAG) | ||
if (!isWeak && _orderedVisits[fromVertex.id] === false) { | ||
if (crumbs.filter(c => (c.entity.id === fromVertex.entity.id) && | ||
(!c.tech || c.tech === fromVertex.tech)).length) { | ||
throw new CircularDependencyError(crumbs.concat(fromVertex)); // TODO: правильно считать цикл | ||
} | ||
} | ||
|
||
// ... if n is marked (i.e. has been visited yet) | ||
if (_orderedVisits[fromVertex.id] !== undefined) { | ||
// ... then already visited | ||
return; | ||
} | ||
|
||
crumbs.push(fromVertex); | ||
|
||
// ... else mark n temporarily. | ||
_orderedVisits[fromVertex.id] = false; | ||
|
||
topo.lookupCreate(fromVertex.id); | ||
|
||
// ... for each node m with an edge from n to m do | ||
const orderedDirectSuccessors = mixedGraph.directSuccessors(fromVertex, { ordered: true, tech }); | ||
|
||
for (let successor of orderedDirectSuccessors) { | ||
// TODO: Try to filter loops earlier | ||
if (successor.id === fromVertex.id) { | ||
continue; | ||
} | ||
|
||
if (isWeak) { | ||
// TODO: Try to speed up this slow piece of shit | ||
const topogroup = topo.lookup(successor.id); | ||
if (topogroup && !topogroup.has(fromVertex.id)) { | ||
// Drop all entities for the current topogroup if came from unordered | ||
for (let id of topo.lookup(successor.id)) { | ||
_orderedVisits[id] = undefined; | ||
} | ||
} | ||
} | ||
|
||
// Add to topogroup for ordered dependencies to sort them later in groups | ||
topo.merge(fromVertex.id, successor.id); | ||
|
||
visit(successor, false); | ||
} | ||
|
||
// ... mark n permanently | ||
// ... unmark n temporarily | ||
_orderedVisits[fromVertex.id] = true; | ||
|
||
// ... add n to head of L (L = ordered, or to tail of unordered) | ||
isWeak | ||
? unorderedSuccessors.add(fromVertex) | ||
: orderedSuccessors.unshift(fromVertex); | ||
|
||
const unorderedDirectSuccessors = mixedGraph.directSuccessors(fromVertex, { ordered: false, tech }); | ||
|
||
for (let successor of unorderedDirectSuccessors) { | ||
// TODO: Try to filter loops earlier | ||
if (successor.id === fromVertex.id || | ||
_orderedVisits[successor.id] || | ||
unorderedSuccessors.has(successor) || | ||
orderedSuccessors.indexOf(successor) !== -1) { | ||
continue; | ||
} | ||
|
||
let _crumbs = crumbs; | ||
crumbs = []; | ||
|
||
visit(successor, true); | ||
|
||
crumbs = _crumbs; | ||
} | ||
|
||
crumbs.pop(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters