Ich mag keine rekursiven Methoden, daher ist DMM nicht verfügbar. Krumelur sieht gut aus, scheint aber viel Speicher zu verbrauchen? Eine alternative stapelbasierte Methode erstellt, die zu funktionieren scheint. Verwendet dieselbe DFS-Logik wie DMM und ich habe diese Lösungen als Vergleich beim Testen verwendet.
public static IEnumerable<T> TopogicalSequenceDFS<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> deps)
{
HashSet<T> yielded = new HashSet<T>();
HashSet<T> visited = new HashSet<T>();
Stack<Tuple<T, IEnumerator<T>>> stack = new Stack<Tuple<T, IEnumerator<T>>>();
foreach (T t in source)
{
stack.Clear();
if (visited.Add(t))
stack.Push(new Tuple<T, IEnumerator<T>>(t, deps(t).GetEnumerator()));
while (stack.Count > 0)
{
var p = stack.Peek();
bool depPushed = false;
while (p.Item2.MoveNext())
{
var curr = p.Item2.Current;
if (visited.Add(curr))
{
stack.Push(new Tuple<T, IEnumerator<T>>(curr, deps(curr).GetEnumerator()));
depPushed = true;
break;
}
else if (!yielded.Contains(curr))
throw new Exception("cycle");
}
if (!depPushed)
{
p = stack.Pop();
if (!yielded.Add(p.Item1))
throw new Exception("bug");
yield return p.Item1;
}
}
}
}
Hier ist auch eine einfachere stapelbasierte BFS-Variante. Es wird ein anderes Ergebnis als das oben genannte erzeugen, aber immer noch gültig. Ich bin mir nicht sicher, ob die Verwendung der oben genannten DFS-Variante einen Vorteil hat, aber es hat Spaß gemacht, sie zu erstellen.
public static IEnumerable<T> TopologicalSequenceBFS<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies)
{
var yielded = new HashSet<T>();
var visited = new HashSet<T>();
var stack = new Stack<Tuple<T, bool>>(source.Select(s => new Tuple<T, bool>(s, false)));
while (stack.Count > 0)
{
var item = stack.Pop();
if (!item.Item2)
{
if (visited.Add(item.Item1))
{
stack.Push(new Tuple<T, bool>(item.Item1, true));
foreach (var dep in dependencies(item.Item1))
stack.Push(new Tuple<T, bool>(dep, false));
}
else if (!yielded.Contains(item.Item1))
throw new Exception("cyclic");
}
else
{
if (!yielded.Add(item.Item1))
throw new Exception("bug");
yield return item.Item1;
}
}
}
Für .NET 4.7+ empfehle ich, Tuple durch ValueTuple zu ersetzen, um weniger Speicher zu verwenden. In älteren .NET-Versionen kann Tuple durch KeyValuePair ersetzt werden.