Skip to content
This repository has been archived by the owner on May 24, 2023. It is now read-only.

Commit

Permalink
Add a secondarySort parameter to topologicalSort (#70)
Browse files Browse the repository at this point in the history
This makes it possible to enforce a topological sort but ensure that
within that, existing nodes are sorted lexically as much as possible.
  • Loading branch information
nex3 committed Feb 9, 2022
1 parent 86ec4ef commit f555487
Show file tree
Hide file tree
Showing 4 changed files with 344 additions and 89 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# 2.1.1-dev
# 2.2.0

* Add a `secondarySort` parameter to the `topologicalSort()` function which
applies an additional lexical sort where that doesn't break the topological
sort.

# 2.1.0

Expand Down
75 changes: 73 additions & 2 deletions lib/src/topological_sort.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import 'dart:collection';

import 'package:collection/collection.dart';
import 'package:collection/collection.dart' hide stronglyConnectedComponents;

import 'cycle_exception.dart';

Expand All @@ -27,9 +27,24 @@ import 'cycle_exception.dart';
/// If you supply one of [equals] or [hashCode], you should generally also to
/// supply the other.
///
/// If you supply [secondarySort], the resulting list will be sorted by that
/// comparison function as much as possible without violating the topological
/// ordering. Note that even with a secondary sort, the result is _still_ not
/// guaranteed to be unique or stable across releases of this package.
///
/// Note: this requires that [nodes] and each iterable returned by [edges]
/// contain no duplicate entries.
///
/// Throws a [CycleException<T>] if the graph is cyclical.
List<T> topologicalSort<T>(Iterable<T> nodes, Iterable<T> Function(T) edges,
{bool Function(T, T)? equals, int Function(T)? hashCode}) {
{bool Function(T, T)? equals,
int Function(T)? hashCode,
Comparator<T>? secondarySort}) {
if (secondarySort != null) {
return _topologicalSortWithSecondary(
[...nodes], edges, secondarySort, equals, hashCode);
}

// https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
var result = QueueList<T>();
var permanentMark = HashSet<T>(equals: equals, hashCode: hashCode);
Expand All @@ -54,3 +69,59 @@ List<T> topologicalSort<T>(Iterable<T> nodes, Iterable<T> Function(T) edges,
}
return result;
}

/// An implementation of [topologicalSort] with a secondary comparison function.
List<T> _topologicalSortWithSecondary<T>(
List<T> nodes,
Iterable<T> Function(T) edges,
Comparator<T> comparator,
bool Function(T, T)? equals,
int Function(T)? hashCode) {
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm,
// modified to sort the nodes to traverse. Also documented in
// https://www.algotree.org/algorithms/tree_graph_traversal/lexical_topological_sort_c++/

// For each node, the number of incoming edges it has that we haven't yet
// traversed.
var incomingEdges = HashMap<T, int>(equals: equals, hashCode: hashCode);
for (var node in nodes) {
for (var child in edges(node)) {
incomingEdges[child] = (incomingEdges[child] ?? 0) + 1;
}
}

// A priority queue of nodes that have no remaining incoming edges.
var nodesToTraverse = PriorityQueue<T>(comparator);
for (var node in nodes) {
if (!incomingEdges.containsKey(node)) nodesToTraverse.add(node);
}

var result = <T>[];
while (nodesToTraverse.isNotEmpty) {
var node = nodesToTraverse.removeFirst();
result.add(node);
for (var child in edges(node)) {
var remainingEdges = incomingEdges[child]!;
remainingEdges--;
incomingEdges[child] = remainingEdges;
if (remainingEdges == 0) nodesToTraverse.add(child);
}
}

if (result.length < nodes.length) {
// This algorithm doesn't automatically produce a cycle list as a side
// effect of sorting, so to throw the appropriate [CycleException] we just
// call the normal [topologicalSort] with a view of this graph that only
// includes nodes that still have edges.
bool nodeIsInCycle(T node) {
var edges = incomingEdges[node];
return edges != null && edges > 0;
}

topologicalSort<T>(nodes.where(nodeIsInCycle), edges,
equals: equals, hashCode: hashCode);
assert(false, 'topologicalSort() should throw if the graph has a cycle');
}

return result;
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: graphs
version: 2.1.1-dev
version: 2.2.0
description: Graph algorithms that operate on graphs in any representation
repository: https://github.com/dart-lang/graphs

Expand Down
Loading

0 comments on commit f555487

Please sign in to comment.