diff --git a/src/graph/DirectedGraph.cs b/src/graph/DirectedGraph.cs index 9aba493..77d89a2 100644 --- a/src/graph/DirectedGraph.cs +++ b/src/graph/DirectedGraph.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -12,8 +13,21 @@ namespace Encompass public class DirectedGraph { + private class SimpleCycleComparer : IEqualityComparer> + { + public bool Equals(IEnumerable x, IEnumerable y) + { + return Enumerable.SequenceEqual(x, y); + } + + public int GetHashCode(IEnumerable obj) + { + return obj.Aggregate(0, (current, next) => current.GetHashCode() ^ next.GetHashCode()); + } + } + protected List _vertices = new List(); - protected Dictionary> _edges = new Dictionary>(); + protected Dictionary> _neighbors = new Dictionary>(); public IEnumerable Vertices { get { return _vertices; } } @@ -26,7 +40,7 @@ namespace Encompass if (!VertexExists(vertex)) { _vertices.Add(vertex); - _edges.Add(vertex, new List()); + _neighbors.Add(vertex, new List()); } } @@ -45,14 +59,25 @@ namespace Encompass public void RemoveVertex(T vertex) { + var edgesToRemove = new List>(); + if (VertexExists(vertex)) { - foreach (var u in Neighbors(vertex)) + foreach (var entry in _neighbors) { - RemoveEdge(vertex, u); + if (entry.Value.Contains(vertex)) + { + edgesToRemove.Add(Tuple.Create(entry.Key, vertex)); + } + } + + foreach (var edge in edgesToRemove) + { + RemoveEdge(edge.Item1, edge.Item2); } _vertices.Remove(vertex); + _neighbors.Remove(vertex); } } @@ -60,7 +85,7 @@ namespace Encompass { if (VertexExists(v) && VertexExists(u)) { - _edges[v].Add(u); + _neighbors[v].Add(u); } } @@ -74,14 +99,14 @@ namespace Encompass public void RemoveEdge(T v, T u) { - _edges[v].Remove(u); + _neighbors[v].Remove(u); } public IEnumerable Neighbors(T vertex) { if (VertexExists(vertex)) { - return _edges[vertex]; + return _neighbors[vertex]; } else { @@ -226,5 +251,163 @@ namespace Encompass return result; } + + public IEnumerable> SimpleCycles() + { + Action, Dictionary>> unblock = null; + unblock = (T thisnode, HashSet blocked, Dictionary> B) => + { + var stack = new Stack(); + stack.Push(thisnode); + + while (stack.Count > 0) + { + var node = stack.Pop(); + if (blocked.Contains(thisnode)) + { + blocked.Remove(thisnode); + if (B.ContainsKey(node)) + { + foreach (var n in B[node]) + { + if (!stack.Contains(n)) + { + stack.Push(n); + } + } + B[node].Clear(); + } + } + } + }; + + List> result = new List>(); + var subGraph = Clone(); + + var sccs = new Stack>(); + foreach (var scc in StronglyConnectedComponents()) + { + sccs.Push(scc); + } + + while (sccs.Count > 0) + { + var scc = new Stack(sccs.Pop()); + var startNode = scc.Pop(); + var path = new Stack(); + path.Push(startNode); + var blocked = new HashSet(); + blocked.Add(startNode); + var closed = new HashSet(); + var B = new Dictionary>(); + var stack = new Stack>>(); + stack.Push(Tuple.Create(startNode, new Stack(subGraph.Neighbors(startNode)))); + + while (stack.Count > 0) + { + var entry = stack.Peek(); + var thisnode = entry.Item1; + var neighbors = entry.Item2; + + if (neighbors.Count > 0) + { + var nextNode = neighbors.Pop(); + + if (nextNode.Equals(startNode)) + { + var resultPath = new List(); + foreach (var v in path) + { + resultPath.Add(v); + } + result.Add(resultPath); + foreach (var v in path) + { + closed.Add(v); + } + } + else if (!blocked.Contains(nextNode)) + { + path.Push(nextNode); + stack.Push(Tuple.Create(nextNode, new Stack(subGraph.Neighbors(nextNode)))); + closed.Remove(nextNode); + blocked.Add(nextNode); + continue; + } + } + + if (neighbors.Count == 0) + { + if (closed.Contains(thisnode)) + { + unblock(thisnode, blocked, B); + } + else + { + foreach (var neighbor in subGraph.Neighbors(thisnode)) + { + if (!B.ContainsKey(neighbor)) + { + B[neighbor] = new HashSet(); + } + B[neighbor].Add(thisnode); + } + } + + stack.Pop(); + path.Pop(); + } + } + + subGraph.RemoveVertex(startNode); + var H = subGraph.SubGraph(scc.ToArray()); + var HSccs = H.StronglyConnectedComponents(); + foreach (var HScc in HSccs) + { + sccs.Push(HScc); + } + } + + return result.Distinct(new SimpleCycleComparer()); + } + + public DirectedGraph Clone() + { + var clone = new DirectedGraph(); + clone.AddVertices(Vertices.ToArray()); + + foreach (var v in Vertices) + { + foreach (var n in Neighbors(v)) + { + clone.AddEdge(v, n); + } + } + + return clone; + } + + public DirectedGraph SubGraph(params T[] subVertices) + { + var subGraph = new DirectedGraph(); + subGraph.AddVertices(subVertices.ToArray()); + + foreach (var v in Vertices) + { + if (Vertices.Contains(v)) + { + var neighbors = Neighbors(v); + foreach (var u in neighbors) + { + if (subVertices.Contains(u)) + { + subGraph.AddEdge(v, u); + } + } + } + } + + return subGraph; + } } } diff --git a/test/DirectedGraphTest.cs b/test/DirectedGraphTest.cs index a7e2652..1aa9bc7 100644 --- a/test/DirectedGraphTest.cs +++ b/test/DirectedGraphTest.cs @@ -77,6 +77,26 @@ namespace Tests Assert.That(myGraph.Neighbors(2), Does.Not.Contain(3)); Assert.That(myGraph.Neighbors(2), Does.Contain(4)); } + + [Test] + public void RemoveVertex() + { + 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.RemoveVertex(2); + + myGraph.Vertices.Should().NotContain(2); + myGraph.Neighbors(1).Should().NotContain(2); + myGraph.Neighbors(3).Should().Contain(4); + } + [Test] public void NodeDFS() { @@ -212,5 +232,119 @@ namespace Tests result.Should().ContainEquivalentOf(sccC); Assert.That(result.Count, Is.EqualTo(3)); } + + [Test] + public void Clone() + { + var myGraph = new DirectedGraph(); + myGraph.AddVertices(1, 2, 3, 4); + myGraph.AddEdges( + Tuple.Create(1, 1), + Tuple.Create(1, 2), + Tuple.Create(2, 3), + Tuple.Create(2, 1), + Tuple.Create(3, 4) + ); + + var clone = myGraph.Clone(); + Assert.That(clone, Is.Not.EqualTo(myGraph)); + clone.Vertices.Should().BeEquivalentTo(1, 2, 3, 4); + clone.Neighbors(1).Should().BeEquivalentTo(1, 2); + clone.Neighbors(2).Should().BeEquivalentTo(3, 1); + clone.Neighbors(3).Should().BeEquivalentTo(4); + } + + [Test] + public void SubGraph() + { + var myGraph = new DirectedGraph(); + myGraph.AddVertices(1, 2, 3, 4); + myGraph.AddEdges( + Tuple.Create(1, 1), + Tuple.Create(1, 2), + Tuple.Create(2, 3), + Tuple.Create(2, 1), + Tuple.Create(3, 4) + ); + + var subGraph = myGraph.SubGraph(1, 2, 3); + subGraph.Vertices.Should().BeEquivalentTo(1, 2, 3); + subGraph.Neighbors(1).Should().BeEquivalentTo(1, 2); + subGraph.Neighbors(2).Should().BeEquivalentTo(1, 3); + subGraph.Neighbors(3).Should().NotContain(4); + } + + [Test] + public void SimpleCyclesSimple() + { + var myGraph = new DirectedGraph(); + myGraph.AddVertices(0, 1, 2); + myGraph.AddEdges( + Tuple.Create(0, 0), + Tuple.Create(0, 1), + Tuple.Create(0, 2), + Tuple.Create(1, 2), + Tuple.Create(2, 0), + Tuple.Create(2, 1), + Tuple.Create(2, 2) + ); + + var result = myGraph.SimpleCycles(); + + var cycleA = new int[] { 0 }; + var cycleB = new int[] { 0, 1, 2 }; + var cycleC = new int[] { 0, 2 }; + var cycleD = new int[] { 1, 2 }; + var cycleE = new int[] { 2 }; + + result.Should().ContainEquivalentOf(cycleA); + result.Should().ContainEquivalentOf(cycleB); + result.Should().ContainEquivalentOf(cycleC); + result.Should().ContainEquivalentOf(cycleD); + result.Should().ContainEquivalentOf(cycleE); + result.Should().HaveCount(5); + } + + [Test] + public void SimpleCyclesComplex() + { + var myGraph = new DirectedGraph(); + myGraph.AddVertices(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + myGraph.AddEdges( + Tuple.Create(0, 1), + Tuple.Create(1, 2), + Tuple.Create(2, 3), + Tuple.Create(3, 0), + Tuple.Create(0, 3), + Tuple.Create(3, 4), + Tuple.Create(4, 5), + Tuple.Create(5, 0), + Tuple.Create(0, 1), + Tuple.Create(1, 6), + Tuple.Create(6, 7), + Tuple.Create(7, 8), + Tuple.Create(8, 0), + Tuple.Create(8, 9) + ); + + var result = myGraph.SimpleCycles(); + var cycleA = new int[] { 0, 3 }; + var cycleB = new int[] { 0, 1, 2, 3, 4, 5 }; + var cycleC = new int[] { 0, 1, 2, 3 }; + var cycleD = new int[] { 0, 3, 4, 5 }; + var cycleE = new int[] { 0, 1, 6, 7, 8 }; + + foreach (var cycle in result) + { + Console.WriteLine(string.Join(", ", cycle)); + } + + result.Should().ContainEquivalentOf(cycleA); + result.Should().ContainEquivalentOf(cycleB); + result.Should().ContainEquivalentOf(cycleC); + result.Should().ContainEquivalentOf(cycleD); + result.Should().ContainEquivalentOf(cycleE); + result.Should().HaveCount(5); + } } }