From cb60c4ebd395e73e9a0714b7300efb884c84104f Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sat, 15 Jun 2019 14:47:55 -0700 Subject: [PATCH] started on directed graph implementation --- src/encompass-cs.csproj | 1 + src/graph/DirectedGraph.cs | 230 +++++++++++++++++++++++++++++++++++++ test/DirectedGraphTest.cs | 216 ++++++++++++++++++++++++++++++++++ test/test.csproj | 2 + 4 files changed, 449 insertions(+) create mode 100644 src/graph/DirectedGraph.cs create mode 100644 test/DirectedGraphTest.cs diff --git a/src/encompass-cs.csproj b/src/encompass-cs.csproj index a625a72..72e33ce 100644 --- a/src/encompass-cs.csproj +++ b/src/encompass-cs.csproj @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/src/graph/DirectedGraph.cs b/src/graph/DirectedGraph.cs new file mode 100644 index 0000000..9aba493 --- /dev/null +++ b/src/graph/DirectedGraph.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Encompass +{ + public enum SearchSymbol + { + start, + finish + } + + public class DirectedGraph + { + protected List _vertices = new List(); + protected Dictionary> _edges = new Dictionary>(); + + public IEnumerable Vertices { get { return _vertices; } } + + /* + * GRAPH STRUCTURE METHODS + */ + + public void AddVertex(T vertex) + { + if (!VertexExists(vertex)) + { + _vertices.Add(vertex); + _edges.Add(vertex, new List()); + } + } + + 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[] edges) + { + foreach (var edge in edges) + { + AddEdge(edge.Item1, edge.Item2); + } + } + + public void RemoveEdge(T v, T u) + { + _edges[v].Remove(u); + } + + public IEnumerable Neighbors(T vertex) + { + if (VertexExists(vertex)) + { + return _edges[vertex]; + } + else + { + return Enumerable.Empty(); + } + } + + /* + * GRAPH ANALYSIS METHODS + */ + + public Dictionary> NodeDFS() + { + var discovered = new HashSet(); + uint time = 0; + var output = new Dictionary>(); + + foreach (var vertex in Vertices) + { + output.Add(vertex, new Dictionary()); + } + + Action 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 TopologicalSort() + { + var dfs = NodeDFS(); + var priority = new SortedList(); + foreach (var entry in dfs) + { + priority.Add(entry.Value[SearchSymbol.finish], entry.Key); + } + return priority.Values.Reverse(); + } + + public IEnumerable> StronglyConnectedComponents() + { + var preorder = new Dictionary(); + var lowlink = new Dictionary(); + var sccFound = new Dictionary(); + var sccQueue = new Stack(); + + var result = new List>(); + + uint preorderCounter = 0; + + foreach (var source in Vertices) + { + if (!sccFound.ContainsKey(source)) + { + var queue = new Stack(); + 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(); + 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; + } + } +} diff --git a/test/DirectedGraphTest.cs b/test/DirectedGraphTest.cs new file mode 100644 index 0000000..a7e2652 --- /dev/null +++ b/test/DirectedGraphTest.cs @@ -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(); + myGraph.AddVertex(4); + + Assert.That(myGraph.Vertices, Does.Contain(4)); + } + + [Test] + public void AddVertices() + { + var myGraph = new DirectedGraph(); + 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(); + myGraph.AddVertices(5, 6); + myGraph.AddEdge(5, 6); + + Assert.That(myGraph.Neighbors(5), Does.Contain(6)); + } + + [Test] + public void AddEdges() + { + var myGraph = new DirectedGraph(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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)); + } + } +} diff --git a/test/test.csproj b/test/test.csproj index 7b887e7..e69bcea 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -5,6 +5,7 @@ false + @@ -13,5 +14,6 @@ + \ No newline at end of file