commit ad8f97e6740363b8685e7d1ded0e01bd0cec930f Author: thatcosmonaut Date: Mon Oct 21 18:48:27 2019 -0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23e54df --- /dev/null +++ b/.gitignore @@ -0,0 +1,222 @@ +# The following command works for downloading when using Git for Windows: +# curl -LOf http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore +# +# Download this file using PowerShell v3 under Windows with the following comand: +# Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore +# +# or wget: +# wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +[Bb]in/ +[Oo]bj/ +# build folder is nowadays used for build scripts and should not be ignored +#build/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# OS generated files # +.DS_Store* +Icon? + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +modulesbin/ +tempbin/ + +# EPiServer Site file (VPP) +AppData/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# vim +*.txt~ +*.swp +*.swo + +# Temp files when opening LibreOffice on ubuntu +.~lock.* + +# svn +.svn + +# CVS - Source Control +**/CVS/ + +# Remainings from resolving conflicts in Source Control +*.orig + +# SQL Server files +**/App_Data/*.mdf +**/App_Data/*.ldf +**/App_Data/*.sdf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +# SASS Compiler cache +.sass-cache + +# Visual Studio 2014 CTP +**/*.sln.ide + +# Visual Studio temp something +.vs/ + +# dotnet stuff +project.lock.json + +# VS 2015+ +*.vc.vc.opendb +*.vc.db + +# Rider +.idea/ + +# Output folder used by Webpack or other FE stuff +**/node_modules/* +**/wwwroot/* + +# SpecFlow specific +*.feature.cs +*.feature.xlsx.* +*.Specs_*.html + +##### +# End of core ignore list, below put you custom 'per project' settings (patterns or path) +##### diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7ab706b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/test/bin/Debug/netcoreapp3.0/test.dll", + "args": [], + "cwd": "${workspaceFolder}/test", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..ce68701 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/test/test.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/test/test.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/test/test.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Graph/DirectedGraph.cs b/Graph/DirectedGraph.cs new file mode 100644 index 0000000..39ac3ad --- /dev/null +++ b/Graph/DirectedGraph.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MoonTools.Core.Graph +{ + public enum SearchSymbol + { + start, + finish + } + + public class DirectedGraph + { + private class SimpleCycleComparer : IEqualityComparer> + { + public bool Equals(IEnumerable x, IEnumerable y) + { + return x.SequenceEqual(y); + } + + public int GetHashCode(IEnumerable obj) + { + return obj.Aggregate(0, (current, next) => current.GetHashCode() ^ next.GetHashCode()); + } + } + + protected List _vertices = new List(); + protected Dictionary> _neighbors = new Dictionary>(); + + public IEnumerable Vertices { get { return _vertices; } } + + /* + * GRAPH STRUCTURE METHODS + */ + + public void AddVertex(T vertex) + { + if (!VertexExists(vertex)) + { + _vertices.Add(vertex); + _neighbors.Add(vertex, new HashSet()); + } + } + + 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) + { + var edgesToRemove = new List>(); + + if (VertexExists(vertex)) + { + foreach (var entry in _neighbors) + { + 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); + } + } + + public void AddEdge(T v, T u) + { + if (VertexExists(v) && VertexExists(u)) + { + _neighbors[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) + { + _neighbors[v].Remove(u); + } + + public IEnumerable Neighbors(T vertex) + { + if (VertexExists(vertex)) + { + return _neighbors[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()); + } + + void dfsHelper(T v) + { + discovered.Add(v); + time++; + output[v].Add(SearchSymbol.start, time); + + foreach (var neighbor in Neighbors(v)) + { + if (!discovered.Contains(neighbor)) + { + dfsHelper(neighbor); + } + } + + time++; + output[v].Add(SearchSymbol.finish, time); + } + + foreach (var vertex in Vertices) + { + if (!discovered.Contains(vertex)) + { + dfsHelper(vertex); + } + } + + return output; + } + + public bool Cyclic() + { + return StronglyConnectedComponents().Any((scc) => scc.Count() > 1); + } + + 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++; + 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 + { + 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; + } + + public IEnumerable> SimpleCycles() + { + void 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 + { + 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/Graph/MoonTools.Core.Graph.csproj b/Graph/MoonTools.Core.Graph.csproj new file mode 100644 index 0000000..72764a6 --- /dev/null +++ b/Graph/MoonTools.Core.Graph.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/Graph/UndirectedGraph.cs b/Graph/UndirectedGraph.cs new file mode 100644 index 0000000..d64e306 --- /dev/null +++ b/Graph/UndirectedGraph.cs @@ -0,0 +1,7 @@ +namespace MoonTools.Core.Graph +{ + public class UndirectedGraph + { + + } +} \ No newline at end of file diff --git a/MoonTools.Core.Graph.sln b/MoonTools.Core.Graph.sln new file mode 100644 index 0000000..ae1de26 --- /dev/null +++ b/MoonTools.Core.Graph.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonTools.Core.Graph", "Graph\MoonTools.Core.Graph.csproj", "{424ACD00-5613-4DBF-8D79-6509D7841D8A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Debug|x64.ActiveCfg = Debug|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Debug|x64.Build.0 = Debug|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Debug|x86.ActiveCfg = Debug|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Debug|x86.Build.0 = Debug|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Release|Any CPU.Build.0 = Release|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Release|x64.ActiveCfg = Release|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Release|x64.Build.0 = Release|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Release|x86.ActiveCfg = Release|Any CPU + {424ACD00-5613-4DBF-8D79-6509D7841D8A}.Release|x86.Build.0 = Release|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Debug|x64.Build.0 = Debug|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Debug|x86.ActiveCfg = Debug|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Debug|x86.Build.0 = Debug|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Release|Any CPU.Build.0 = Release|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Release|x64.ActiveCfg = Release|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Release|x64.Build.0 = Release|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Release|x86.ActiveCfg = Release|Any CPU + {423B5FFF-5B19-4D1C-ACF7-B5908E3E50EA}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/test/DirectedGraphTest.cs b/test/DirectedGraphTest.cs new file mode 100644 index 0000000..6793fc6 --- /dev/null +++ b/test/DirectedGraphTest.cs @@ -0,0 +1,373 @@ +using NUnit.Framework; +using FluentAssertions; + +using System; +using System.Linq; + +using MoonTools.Core.Graph; + +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 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() + { + 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)); + } + + [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 }; + + 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 Cyclic() + { + var myGraph = new DirectedGraph(); + myGraph.AddVertices(1, 2, 3, 4); + myGraph.AddEdges( + Tuple.Create(1, 2), + Tuple.Create(2, 3), + Tuple.Create(3, 1), + Tuple.Create(3, 4) + ); + + Assert.That(myGraph.Cyclic(), Is.True); + } + + [Test] + public void Acyclic() + { + var myGraph = new DirectedGraph(); + myGraph.AddVertices(1, 2, 3, 4); + myGraph.AddEdges( + Tuple.Create(1, 2), + Tuple.Create(2, 3), + Tuple.Create(3, 4) + ); + + Assert.That(myGraph.Cyclic(), Is.False); + } + } +} diff --git a/test/test.csproj b/test/test.csproj new file mode 100644 index 0000000..243ce77 --- /dev/null +++ b/test/test.csproj @@ -0,0 +1,15 @@ + + + netcoreapp3.0 + false + + + + + + + + + + + \ No newline at end of file