Rewrite topo sort as reverse post-order DFS
This commit is contained in:
parent
8e95fa492f
commit
b8c0a2c82a
1 changed files with 21 additions and 35 deletions
|
|
@ -80,51 +80,37 @@ export class Commits {
|
||||||
*
|
*
|
||||||
* Assumes that there are no duplicated commits anywhere.
|
* Assumes that there are no duplicated commits anywhere.
|
||||||
*
|
*
|
||||||
* The algorithm used is a version of [Kahn's algorithm][0] that starts at the
|
* A reverse post-order DFS is a topological sort, so that is what this
|
||||||
* nodes with no parents. It uses a stack for the set of parentless nodes,
|
* function implements.
|
||||||
* meaning the resulting commit order is depth-first-y, not breadth-first-y.
|
|
||||||
* For example, this commit graph (where children are ordered top to bottom)
|
|
||||||
* results in the order `A, B, C, D, E, F` and not an interleaved order like
|
|
||||||
* `A, B, D, C, E, F` (which a queue would produce):
|
|
||||||
*
|
|
||||||
* ```text
|
|
||||||
* A - B - C
|
|
||||||
* \ \
|
|
||||||
* D - E - F
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* [0]: https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
|
|
||||||
*/
|
*/
|
||||||
#sortCommitsTopologically(commits: Commit[]): Commit[] {
|
#sortCommitsTopologically(commits: Commit[]): Commit[] {
|
||||||
// Track which unvisited parents are left for each commit
|
const visited: Set<string> = new Set();
|
||||||
const childParentMap: Map<string, Set<string>> = new Map();
|
const visiting: Commit[] = commits.filter(c => c.parents.length == 0);
|
||||||
for (const commit of commits) {
|
|
||||||
childParentMap.set(commit.hash, new Set(commit.parents.map(p => p.hash)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stack of parentless commits
|
|
||||||
const parentless = commits.filter(c => c.parents.length == 0);
|
|
||||||
|
|
||||||
const sorted: Commit[] = [];
|
const sorted: Commit[] = [];
|
||||||
while (parentless.length > 0) {
|
|
||||||
// Visit commit
|
while (visiting.length > 0) {
|
||||||
const commit = parentless.pop()!;
|
const commit = visiting.at(-1)!;
|
||||||
|
if (visited.has(commit.hash)) {
|
||||||
|
visiting.pop();
|
||||||
sorted.push(commit);
|
sorted.push(commit);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (const child of commit.children) {
|
for (const child of commit.children) {
|
||||||
const parents = childParentMap.get(child.hash)!;
|
if (!visited.has(child.hash)) {
|
||||||
parents.delete(commit.hash);
|
visiting.push(child);
|
||||||
if (parents.size == 0) {
|
|
||||||
parentless.push(child);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [child, parents] of childParentMap.entries()) {
|
visited.add(commit.hash);
|
||||||
console.assert(parents.size == 0, child, "still has parents");
|
|
||||||
}
|
}
|
||||||
console.assert(parentless.length == 0);
|
|
||||||
console.assert(commits.length == sorted.length, "topo sort changed commit amount");
|
sorted.reverse();
|
||||||
|
|
||||||
|
console.assert(visited.size === commits.length);
|
||||||
|
console.assert(visiting.length === 0);
|
||||||
|
console.assert(sorted.length === commits.length);
|
||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue