started on directed graph implementation

pull/5/head
Evan Hemsley 2019-06-15 14:47:55 -07:00
parent 700f10f31e
commit cb60c4ebd3
4 changed files with 449 additions and 0 deletions

View File

@ -15,5 +15,6 @@
<Content Include="Engine.cs" />
<Content Include="attributes\Mutates.cs" />
<Content Include="exceptions\IllegalComponentMutationException.cs" />
<Content Include="graph\DirectedGraph.cs" />
</ItemGroup>
</Project>

230
src/graph/DirectedGraph.cs Normal file
View File

@ -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;
}
}
}

216
test/DirectedGraphTest.cs Normal file
View File

@ -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));
}
}
}

View File

@ -5,6 +5,7 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.7.0" />
<PackageReference Include="nunit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
@ -13,5 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\src\encompass-cs.csproj" />
<Content Include="EngineTest.cs" />
<Content Include="DirectedGraphTest.cs" />
</ItemGroup>
</Project>