Skip to content

Commit 56b6fa0

Browse files
committed
Topological sort
Due to Contribution.md I would like to send you Topological sort algorithm (it is missing from the table). Appropriate tests are included
1 parent 69ea5f0 commit 56b6fa0

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Linq;
2+
using FluentAssertions;
3+
using Xunit;
4+
5+
namespace Algorithms.Tests.TopologicalSortTests
6+
{
7+
public class TopSortTests
8+
{
9+
[Fact]
10+
public void TopSort_SortItems_ReturnResult()
11+
{
12+
var a = new Item("A");
13+
var b = new Item("B", "C", "E");
14+
var c = new Item("C");
15+
var d = new Item("D", "A");
16+
var e = new Item("E", "D", "G");
17+
var f = new Item("F");
18+
var g = new Item("G", "F", "H");
19+
var h = new Item("H");
20+
var unsorted = new[] { a, b, c, d, e, f, g, h };
21+
var expected = "ACDFHGEB";
22+
23+
var sortedItems = TopologicalSort.Sort(unsorted, x => x.Dependencies, x => x.Name);
24+
var data = sortedItems.Aggregate("", (current, group) => current + group.Name);
25+
26+
data.Should().Be(expected);
27+
}
28+
29+
[Fact]
30+
public void TopSort_GroupItems_ReturnResult()
31+
{
32+
var a = new Item("A");
33+
var b = new Item("B", "C", "E");
34+
var c = new Item("C");
35+
var d = new Item("D", "A");
36+
var e = new Item("E", "D", "G");
37+
var f = new Item("F");
38+
var g = new Item("G", "F", "H");
39+
var h = new Item("H");
40+
var unsorted = new[] { a, b, c, d, e, f, g, h };
41+
var expected = "ACFH/DG/E/B/";
42+
43+
var grouped = TopologicalSort.Group(unsorted, x => x.Dependencies, x => x.Name);
44+
var data = "";
45+
foreach (var group in grouped)
46+
{
47+
data = group.Aggregate(data, (current, item) => current + item.Name);
48+
data += "/";
49+
}
50+
51+
data.Should().Be(expected);
52+
}
53+
}
54+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Algorithms
5+
{
6+
public class GenericEqualityComparer<TItem, TKey> : EqualityComparer<TItem>
7+
{
8+
private readonly Func<TItem, TKey> _getKey;
9+
private readonly EqualityComparer<TKey> _keyComparer;
10+
11+
public GenericEqualityComparer(Func<TItem, TKey> getKey)
12+
{
13+
_getKey = getKey;
14+
_keyComparer = EqualityComparer<TKey>.Default;
15+
}
16+
17+
public override bool Equals(TItem x, TItem y)
18+
{
19+
if (x == null && y == null)
20+
{
21+
return true;
22+
}
23+
if (x == null || y == null)
24+
{
25+
return false;
26+
}
27+
return _keyComparer.Equals(_getKey(x), _getKey(y));
28+
}
29+
30+
public override int GetHashCode(TItem obj)
31+
{
32+
if (obj == null)
33+
{
34+
return 0;
35+
}
36+
return _keyComparer.GetHashCode(_getKey(obj));
37+
}
38+
}
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Algorithms
2+
{
3+
public class Item
4+
{
5+
public string Name { get; }
6+
public string[] Dependencies { get; }
7+
8+
public Item(string name, params string[] dependencies)
9+
{
10+
Name = name;
11+
Dependencies = dependencies;
12+
}
13+
}
14+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Linq;
5+
6+
namespace Algorithms
7+
{
8+
public static class TopologicalSort
9+
{
10+
public static IList<T> Sort<T, TKey>(IEnumerable<T> source, Func<T, IEnumerable<TKey>> getDependencies,
11+
Func<T, TKey> getKey, bool ignoreCycles = false)
12+
{
13+
ICollection<T> source2 = (source as ICollection<T>) ?? source.ToArray();
14+
return Sort<T>(source2, RemapDependencies(source2, getDependencies, getKey), null, ignoreCycles);
15+
}
16+
17+
private static IList<T> Sort<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies,
18+
IEqualityComparer<T> comparer = null, bool ignoreCycles = false)
19+
{
20+
var sorted = new List<T>();
21+
var visited = new Dictionary<T, bool>(comparer);
22+
23+
foreach (var item in source)
24+
{
25+
Visit(item, getDependencies, sorted, visited, ignoreCycles);
26+
}
27+
28+
return sorted;
29+
}
30+
31+
private static void Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted,
32+
Dictionary<T, bool> visited, bool ignoreCycles)
33+
{
34+
bool inProcess;
35+
var alreadyVisited = visited.TryGetValue(item, out inProcess);
36+
37+
if (alreadyVisited)
38+
{
39+
if (inProcess && !ignoreCycles)
40+
{
41+
throw new ArgumentException("Cyclic dependency found.");
42+
}
43+
}
44+
else
45+
{
46+
visited[item] = true;
47+
48+
var dependencies = getDependencies(item);
49+
if (dependencies != null)
50+
{
51+
foreach (var dependency in dependencies)
52+
{
53+
Visit(dependency, getDependencies, sorted, visited, ignoreCycles);
54+
}
55+
}
56+
57+
visited[item] = false;
58+
sorted.Add(item);
59+
}
60+
}
61+
62+
public static IList<ICollection<T>> Group<T, TKey>(IEnumerable<T> source,
63+
Func<T, IEnumerable<TKey>> getDependencies, Func<T, TKey> getKey, bool ignoreCycles = true)
64+
{
65+
ICollection<T> source2 = (source as ICollection<T>) ?? source.ToArray();
66+
return Group<T>(source2, RemapDependencies(source2, getDependencies, getKey), null, ignoreCycles);
67+
}
68+
69+
private static IList<ICollection<T>> Group<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies,
70+
IEqualityComparer<T> comparer = null, bool ignoreCycles = true)
71+
{
72+
var sorted = new List<ICollection<T>>();
73+
var visited = new Dictionary<T, int>(comparer);
74+
75+
foreach (var item in source)
76+
{
77+
Visit(item, getDependencies, sorted, visited, ignoreCycles);
78+
}
79+
80+
return sorted;
81+
}
82+
83+
private static Func<T, IEnumerable<T>> RemapDependencies<T, TKey>(IEnumerable<T> source, Func<T, IEnumerable<TKey>> getDependencies, Func<T, TKey> getKey)
84+
{
85+
var map = source.ToDictionary(getKey);
86+
return item =>
87+
{
88+
var dependencies = getDependencies(item);
89+
return dependencies != null
90+
? dependencies.Select(key => map[key])
91+
: null;
92+
};
93+
}
94+
95+
private static int Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<ICollection<T>> sorted,
96+
Dictionary<T, int> visited, bool ignoreCycles)
97+
{
98+
const int inProcess = -1;
99+
int level;
100+
var alreadyVisited = visited.TryGetValue(item, out level);
101+
102+
if (alreadyVisited)
103+
{
104+
if (level == inProcess && ignoreCycles)
105+
{
106+
throw new ArgumentException("Cyclic dependency found.");
107+
}
108+
}
109+
else
110+
{
111+
visited[item] = (level = inProcess);
112+
113+
var dependencies = getDependencies(item);
114+
if (dependencies != null)
115+
{
116+
foreach (var dependency in dependencies)
117+
{
118+
var depLevel = Visit(dependency, getDependencies, sorted, visited, ignoreCycles);
119+
level = Math.Max(level, depLevel);
120+
}
121+
}
122+
123+
visited[item] = ++level;
124+
while (sorted.Count <= level)
125+
{
126+
sorted.Add(new Collection<T>());
127+
}
128+
sorted[level].Add(item);
129+
}
130+
131+
return level;
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)