started on directed graph implementation
parent
700f10f31e
commit
cb60c4ebd3
|
@ -15,5 +15,6 @@
|
||||||
<Content Include="Engine.cs" />
|
<Content Include="Engine.cs" />
|
||||||
<Content Include="attributes\Mutates.cs" />
|
<Content Include="attributes\Mutates.cs" />
|
||||||
<Content Include="exceptions\IllegalComponentMutationException.cs" />
|
<Content Include="exceptions\IllegalComponentMutationException.cs" />
|
||||||
|
<Content Include="graph\DirectedGraph.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -0,0 +1,230 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Encompass
|
||||||
|
{
|
||||||
|
public enum SearchSymbol
|
||||||
|
{
|
||||||
|
start,
|
||||||
|
finish
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DirectedGraph<T>
|
||||||
|
{
|
||||||
|
protected List<T> _vertices = new List<T>();
|
||||||
|
protected Dictionary<T, List<T>> _edges = new Dictionary<T, List<T>>();
|
||||||
|
|
||||||
|
public IEnumerable<T> Vertices { get { return _vertices; } }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GRAPH STRUCTURE METHODS
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void AddVertex(T vertex)
|
||||||
|
{
|
||||||
|
if (!VertexExists(vertex))
|
||||||
|
{
|
||||||
|
_vertices.Add(vertex);
|
||||||
|
_edges.Add(vertex, new List<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddVertices(params T[] vertices)
|
||||||
|
{
|
||||||
|
foreach (var vertex in vertices)
|
||||||
|
{
|
||||||
|
AddVertex(vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool VertexExists(T vertex)
|
||||||
|
{
|
||||||
|
return Vertices.Contains(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveVertex(T vertex)
|
||||||
|
{
|
||||||
|
if (VertexExists(vertex))
|
||||||
|
{
|
||||||
|
foreach (var u in Neighbors(vertex))
|
||||||
|
{
|
||||||
|
RemoveEdge(vertex, u);
|
||||||
|
}
|
||||||
|
|
||||||
|
_vertices.Remove(vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEdge(T v, T u)
|
||||||
|
{
|
||||||
|
if (VertexExists(v) && VertexExists(u))
|
||||||
|
{
|
||||||
|
_edges[v].Add(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEdges(params Tuple<T, T>[] edges)
|
||||||
|
{
|
||||||
|
foreach (var edge in edges)
|
||||||
|
{
|
||||||
|
AddEdge(edge.Item1, edge.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEdge(T v, T u)
|
||||||
|
{
|
||||||
|
_edges[v].Remove(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<T> Neighbors(T vertex)
|
||||||
|
{
|
||||||
|
if (VertexExists(vertex))
|
||||||
|
{
|
||||||
|
return _edges[vertex];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GRAPH ANALYSIS METHODS
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Dictionary<T, Dictionary<SearchSymbol, uint>> NodeDFS()
|
||||||
|
{
|
||||||
|
var discovered = new HashSet<T>();
|
||||||
|
uint time = 0;
|
||||||
|
var output = new Dictionary<T, Dictionary<SearchSymbol, uint>>();
|
||||||
|
|
||||||
|
foreach (var vertex in Vertices)
|
||||||
|
{
|
||||||
|
output.Add(vertex, new Dictionary<SearchSymbol, uint>());
|
||||||
|
}
|
||||||
|
|
||||||
|
Action<T> dfsHelper = null;
|
||||||
|
dfsHelper = (T v) =>
|
||||||
|
{
|
||||||
|
discovered.Add(v);
|
||||||
|
time += 1;
|
||||||
|
output[v].Add(SearchSymbol.start, time);
|
||||||
|
|
||||||
|
foreach (var neighbor in Neighbors(v))
|
||||||
|
{
|
||||||
|
if (!discovered.Contains(neighbor))
|
||||||
|
{
|
||||||
|
dfsHelper(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time += 1;
|
||||||
|
output[v].Add(SearchSymbol.finish, time);
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var vertex in Vertices)
|
||||||
|
{
|
||||||
|
if (!discovered.Contains(vertex))
|
||||||
|
{
|
||||||
|
dfsHelper(vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<T> TopologicalSort()
|
||||||
|
{
|
||||||
|
var dfs = NodeDFS();
|
||||||
|
var priority = new SortedList<uint, T>();
|
||||||
|
foreach (var entry in dfs)
|
||||||
|
{
|
||||||
|
priority.Add(entry.Value[SearchSymbol.finish], entry.Key);
|
||||||
|
}
|
||||||
|
return priority.Values.Reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IEnumerable<T>> StronglyConnectedComponents()
|
||||||
|
{
|
||||||
|
var preorder = new Dictionary<T, uint>();
|
||||||
|
var lowlink = new Dictionary<T, uint>();
|
||||||
|
var sccFound = new Dictionary<T, bool>();
|
||||||
|
var sccQueue = new Stack<T>();
|
||||||
|
|
||||||
|
var result = new List<List<T>>();
|
||||||
|
|
||||||
|
uint preorderCounter = 0;
|
||||||
|
|
||||||
|
foreach (var source in Vertices)
|
||||||
|
{
|
||||||
|
if (!sccFound.ContainsKey(source))
|
||||||
|
{
|
||||||
|
var queue = new Stack<T>();
|
||||||
|
queue.Push(source);
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var v = queue.Peek();
|
||||||
|
if (!preorder.ContainsKey(v))
|
||||||
|
{
|
||||||
|
preorderCounter += 1;
|
||||||
|
preorder[v] = preorderCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
var done = true;
|
||||||
|
var vNeighbors = Neighbors(v);
|
||||||
|
foreach (var w in vNeighbors)
|
||||||
|
{
|
||||||
|
if (!preorder.ContainsKey(w))
|
||||||
|
{
|
||||||
|
queue.Push(w);
|
||||||
|
done = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done)
|
||||||
|
{
|
||||||
|
lowlink[v] = preorder[v];
|
||||||
|
foreach (var w in vNeighbors)
|
||||||
|
{
|
||||||
|
if (!sccFound.ContainsKey(w))
|
||||||
|
{
|
||||||
|
if (preorder[w] > preorder[v])
|
||||||
|
{
|
||||||
|
lowlink[v] = Math.Min(lowlink[v], lowlink[w]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lowlink[v] = Math.Min(lowlink[v], preorder[w]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queue.Pop();
|
||||||
|
if (lowlink[v] == preorder[v])
|
||||||
|
{
|
||||||
|
sccFound[v] = true;
|
||||||
|
var scc = new List<T>();
|
||||||
|
scc.Add(v);
|
||||||
|
while (sccQueue.Count > 0 && preorder[sccQueue.Peek()] > preorder[v])
|
||||||
|
{
|
||||||
|
var k = sccQueue.Pop();
|
||||||
|
sccFound[k] = true;
|
||||||
|
scc.Add(k);
|
||||||
|
}
|
||||||
|
result.Add(scc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sccQueue.Push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Encompass;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Tests
|
||||||
|
{
|
||||||
|
public class DirectedGraphTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void AddVertex()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertex(4);
|
||||||
|
|
||||||
|
Assert.That(myGraph.Vertices, Does.Contain(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AddVertices()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(4, 20, 69);
|
||||||
|
|
||||||
|
Assert.IsTrue(myGraph.VertexExists(4));
|
||||||
|
Assert.IsTrue(myGraph.VertexExists(20));
|
||||||
|
Assert.IsTrue(myGraph.VertexExists(69));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AddEdge()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(5, 6);
|
||||||
|
myGraph.AddEdge(5, 6);
|
||||||
|
|
||||||
|
Assert.That(myGraph.Neighbors(5), Does.Contain(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AddEdges()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(1, 2, 3, 4);
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(2, 4),
|
||||||
|
Tuple.Create(3, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.That(myGraph.Neighbors(1), Does.Contain(2));
|
||||||
|
Assert.That(myGraph.Neighbors(2), Does.Contain(3));
|
||||||
|
Assert.That(myGraph.Neighbors(2), Does.Contain(4));
|
||||||
|
Assert.That(myGraph.Neighbors(3), Does.Contain(4));
|
||||||
|
Assert.That(myGraph.Neighbors(1), Does.Not.Contain(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RemoveEdge()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<int>();
|
||||||
|
myGraph.AddVertices(1, 2, 3, 4);
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(2, 4),
|
||||||
|
Tuple.Create(3, 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
myGraph.RemoveEdge(2, 3);
|
||||||
|
|
||||||
|
Assert.That(myGraph.Neighbors(2), Does.Not.Contain(3));
|
||||||
|
Assert.That(myGraph.Neighbors(2), Does.Contain(4));
|
||||||
|
}
|
||||||
|
[Test]
|
||||||
|
public void NodeDFS()
|
||||||
|
{
|
||||||
|
var myGraph = new DirectedGraph<char>();
|
||||||
|
myGraph.AddVertices('a', 'b', 'c', 'd');
|
||||||
|
myGraph.AddEdges(
|
||||||
|
Tuple.Create('a', 'b'),
|
||||||
|
Tuple.Create('a', 'c'),
|
||||||
|
Tuple.Create('b', 'd')
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = myGraph.NodeDFS();
|
||||||
|
|
||||||
|
Assert.That(result['a'][SearchSymbol.start], Is.EqualTo(1));
|
||||||
|
Assert.That(result['a'][SearchSymbol.finish], Is.EqualTo(8));
|
||||||
|
|
||||||
|
Assert.That(result['b'][SearchSymbol.start], Is.EqualTo(2));
|
||||||
|
Assert.That(result['b'][SearchSymbol.finish], Is.EqualTo(5));
|
||||||
|
|
||||||
|
Assert.That(result['c'][SearchSymbol.start], Is.EqualTo(6));
|
||||||
|
Assert.That(result['c'][SearchSymbol.finish], Is.EqualTo(7));
|
||||||
|
|
||||||
|
Assert.That(result['d'][SearchSymbol.start], Is.EqualTo(3));
|
||||||
|
Assert.That(result['d'][SearchSymbol.finish], Is.EqualTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TopologicalSortSimple()
|
||||||
|
{
|
||||||
|
var simpleGraph = new DirectedGraph<char>();
|
||||||
|
simpleGraph.AddVertices('a', 'b', 'c', 'd');
|
||||||
|
simpleGraph.AddEdges(
|
||||||
|
Tuple.Create('a', 'b'),
|
||||||
|
Tuple.Create('a', 'c'),
|
||||||
|
Tuple.Create('b', 'a'),
|
||||||
|
Tuple.Create('b', 'd')
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.That(simpleGraph.TopologicalSort(), Is.EqualTo(new char[] { 'a', 'c', 'b', 'd' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TopologicalSortComplex()
|
||||||
|
{
|
||||||
|
var complexGraph = new DirectedGraph<char>();
|
||||||
|
complexGraph.AddVertices('a', 'b', 'c', 'd', 'e', 'f', 'g', 't', 'm');
|
||||||
|
complexGraph.AddEdges(
|
||||||
|
Tuple.Create('a', 'b'),
|
||||||
|
Tuple.Create('a', 'c'),
|
||||||
|
Tuple.Create('a', 'd'),
|
||||||
|
Tuple.Create('b', 'f'),
|
||||||
|
Tuple.Create('b', 'g'),
|
||||||
|
Tuple.Create('c', 'g'),
|
||||||
|
Tuple.Create('e', 't'),
|
||||||
|
Tuple.Create('t', 'm')
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
complexGraph.TopologicalSort(),
|
||||||
|
Is.EqualTo(new char[] { 'e', 't', 'm', 'a', 'd', 'c', 'b', 'g', 'f' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void StronglyConnectedComponentsSimple()
|
||||||
|
{
|
||||||
|
var simpleGraph = new DirectedGraph<int>();
|
||||||
|
simpleGraph.AddVertices(1, 2, 3);
|
||||||
|
simpleGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(3, 2),
|
||||||
|
Tuple.Create(2, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = simpleGraph.StronglyConnectedComponents();
|
||||||
|
var scc = new int[] { 1, 2, 3 };
|
||||||
|
|
||||||
|
result.Should().ContainEquivalentOf(scc);
|
||||||
|
Assert.That(result.Count, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void StronglyConnectedComponentsMedium()
|
||||||
|
{
|
||||||
|
var mediumGraph = new DirectedGraph<int>();
|
||||||
|
mediumGraph.AddVertices(1, 2, 3, 4);
|
||||||
|
mediumGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(1, 3),
|
||||||
|
Tuple.Create(1, 4),
|
||||||
|
Tuple.Create(4, 2),
|
||||||
|
Tuple.Create(3, 4),
|
||||||
|
Tuple.Create(2, 3)
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = mediumGraph.StronglyConnectedComponents();
|
||||||
|
var sccA = new int[] { 2, 3, 4 };
|
||||||
|
var sccB = new int[] { 1 };
|
||||||
|
|
||||||
|
result.Should().ContainEquivalentOf(sccA);
|
||||||
|
result.Should().ContainEquivalentOf(sccB);
|
||||||
|
Assert.That(result.Count, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void StronglyConnectedComponentsComplex()
|
||||||
|
{
|
||||||
|
var complexGraph = new DirectedGraph<int>();
|
||||||
|
complexGraph.AddVertices(1, 2, 3, 4, 5, 6, 7, 8);
|
||||||
|
complexGraph.AddEdges(
|
||||||
|
Tuple.Create(1, 2),
|
||||||
|
Tuple.Create(2, 3),
|
||||||
|
Tuple.Create(2, 8),
|
||||||
|
Tuple.Create(3, 4),
|
||||||
|
Tuple.Create(3, 7),
|
||||||
|
Tuple.Create(4, 5),
|
||||||
|
Tuple.Create(5, 3),
|
||||||
|
Tuple.Create(5, 6),
|
||||||
|
Tuple.Create(7, 4),
|
||||||
|
Tuple.Create(7, 6),
|
||||||
|
Tuple.Create(8, 1),
|
||||||
|
Tuple.Create(8, 7)
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = complexGraph.StronglyConnectedComponents();
|
||||||
|
var sccA = new int[] { 3, 4, 5, 7 };
|
||||||
|
var sccB = new int[] { 1, 2, 8 };
|
||||||
|
var sccC = new int[] { 6 };
|
||||||
|
|
||||||
|
result.Should().ContainEquivalentOf(sccA);
|
||||||
|
result.Should().ContainEquivalentOf(sccB);
|
||||||
|
result.Should().ContainEquivalentOf(sccC);
|
||||||
|
Assert.That(result.Count, Is.EqualTo(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="5.7.0" />
|
||||||
<PackageReference Include="nunit" Version="3.11.0" />
|
<PackageReference Include="nunit" Version="3.11.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||||
|
@ -13,5 +14,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\src\encompass-cs.csproj" />
|
<ProjectReference Include="..\src\encompass-cs.csproj" />
|
||||||
<Content Include="EngineTest.cs" />
|
<Content Include="EngineTest.cs" />
|
||||||
|
<Content Include="DirectedGraphTest.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
Loading…
Reference in New Issue