Skip to content

Commit 0ea3171

Browse files
committed
Generic graph representation for additional algorithms
1 parent 8ff064f commit 0ea3171

File tree

10 files changed

+138
-32
lines changed

10 files changed

+138
-32
lines changed

CodeParser/Analysis/Cycles/CycleFinder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CodeParser.Analysis.Shared;
22
using Contracts.Graph;
3+
using GraphLib.Algorithms.StronglyConnectedComponents;
34

45
namespace CodeParser.Analysis.Cycles;
56

@@ -35,7 +36,7 @@ public static List<CycleGroup> FindCycleGroups(CodeGraph originalGraph)
3536
/// When determining the SCC we collected the relevant nodes, but did not
3637
/// remove the orphaned dependencies, yet.
3738
/// </summary>
38-
private static void RemoveOrphanedDependencies(Scc scc)
39+
private static void RemoveOrphanedDependencies(Scc<SearchNode> scc)
3940
{
4041
var existingIds = scc.Vertices.Select(v => v.Id).ToHashSet();
4142
foreach (var vertex in scc.Vertices)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using CodeParser.Analysis.Shared;
2+
using GraphLib.Contracts;
3+
4+
namespace CodeParser.Analysis.Cycles;
5+
6+
public class SearchGraph : IGraphRepresentation<SearchNode>
7+
{
8+
public SearchGraph(List<SearchNode> vertices)
9+
{
10+
Vertices = vertices;
11+
}
12+
13+
public uint VertexCount => (uint)Vertices.Count();
14+
15+
public List<SearchNode> Vertices { get; }
16+
17+
public IReadOnlyCollection<SearchNode> GetNeighbors(SearchNode vertex)
18+
{
19+
return vertex.Dependencies;
20+
}
21+
22+
public IReadOnlyCollection<SearchNode> GetVertices()
23+
{
24+
return Vertices;
25+
}
26+
27+
public bool IsEdge(SearchNode source, SearchNode target)
28+
{
29+
return source.Dependencies.Contains(target);
30+
}
31+
32+
public bool IsVertex(SearchNode vertex)
33+
{
34+
return Vertices.Contains(vertex);
35+
}
36+
}

CodeParser/Analysis/Cycles/SearchGraphBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
namespace CodeParser.Analysis.Cycles;
55

6-
public class SearchGraphBuilder
6+
public partial class SearchGraphBuilder
77
{
8-
public static List<SearchNode> BuildSearchGraph(CodeGraph codeGraph)
8+
9+
public static SearchGraph BuildSearchGraph(CodeGraph codeGraph)
910
{
1011
var searchNodes = new Dictionary<string, SearchNode>();
1112

@@ -41,7 +42,7 @@ public static List<SearchNode> BuildSearchGraph(CodeGraph codeGraph)
4142
searchSource.Dependencies.Add(searchTarget);
4243
}
4344

44-
return searchNodes.Values.ToList();
45+
return new SearchGraph(searchNodes.Values.ToList());
4546
}
4647

4748
private static bool IsMethod(CodeGraph codeGraph, string id)

CodeParser/Analysis/Shared/Tarjan.cs

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,49 @@
1-
namespace CodeParser.Analysis.Shared;
1+
using GraphLib.Contracts;
22

3-
public class Scc
3+
namespace GraphLib.Algorithms.StronglyConnectedComponents;
4+
5+
/// <summary>
6+
/// Returns the strongly connected components.
7+
/// Cycles are restricted by these components.
8+
/// </summary>
9+
public class Scc<TVertex>
410
{
5-
public HashSet<SearchNode> Vertices { get; } = [];
11+
public HashSet<TVertex> Vertices = new();
612
}
713

814
public static class Tarjan
915
{
1016
/// <summary>
11-
/// O(|E| + |V|)
17+
/// O(|E| + |V|)
1218
/// </summary>
13-
public static List<Scc> FindStronglyConnectedComponents(List<SearchNode> graph)
19+
public static List<Scc<TVertex>> FindStronglyConnectedComponents<TVertex>(IGraphRepresentation<TVertex> graph)
20+
where TVertex : notnull
1421
{
15-
var sccs = new List<Scc>();
22+
var sccs = new List<Scc<TVertex>>();
1623

17-
var idMap = new Dictionary<SearchNode, int>();
18-
var lowMap = new Dictionary<SearchNode, int>();
19-
var stack = new Stack<SearchNode>();
20-
var inStack = new HashSet<SearchNode>();
24+
var idMap = new Dictionary<TVertex, int>();
25+
var lowMap = new Dictionary<TVertex, int>();
26+
var stack = new Stack<TVertex>();
27+
var inStack = new HashSet<TVertex>();
2128

22-
foreach (var vertex in graph)
29+
foreach (var vertex in graph.GetVertices())
2330
{
2431
if (!idMap.ContainsKey(vertex))
2532
{
26-
Dfs(vertex, idMap, lowMap, stack, inStack, sccs);
33+
Dfs(graph, vertex, idMap, lowMap, stack, inStack, sccs);
2734
}
2835
}
2936

3037
return sccs;
3138
}
3239

33-
private static void Dfs(SearchNode u,
34-
Dictionary<SearchNode, int> idMap,
35-
Dictionary<SearchNode, int> lowMap,
36-
Stack<SearchNode> stack,
37-
HashSet<SearchNode> inStack, // visited
38-
List<Scc> sccs)
40+
private static void Dfs<TVertex>(IGraphRepresentation<TVertex> graph,
41+
TVertex u,
42+
Dictionary<TVertex, int> idMap,
43+
Dictionary<TVertex, int> lowMap,
44+
Stack<TVertex> stack,
45+
HashSet<TVertex> inStack, // visited
46+
List<Scc<TVertex>> sccs) where TVertex : notnull
3947

4048

4149
{
@@ -45,12 +53,12 @@ private static void Dfs(SearchNode u,
4553
stack.Push(u);
4654
inStack.Add(u);
4755

48-
foreach (var v in u.Dependencies)
56+
foreach (var v in graph.GetNeighbors(u))
4957
{
5058
if (idMap.ContainsKey(v) is false)
5159
{
5260
// Unvisited vertex
53-
Dfs(v, idMap, lowMap, stack, inStack, sccs);
61+
Dfs(graph, v, idMap, lowMap, stack, inStack, sccs);
5462
lowMap[u] = Math.Min(lowMap[u], lowMap[v]);
5563
}
5664
else if (inStack.Contains(v))
@@ -64,7 +72,7 @@ private static void Dfs(SearchNode u,
6472
if (lowMap[u] == idMap[u])
6573
{
6674
// Vertex is root of SCC.
67-
var scc = new Scc();
75+
var scc = new Scc<TVertex>();
6876
while (stack.Any())
6977
{
7078
var popped = stack.Pop();

CodeParserTests/CodeGraphBuilderTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void GenerateDetailedCodeGraph_SimpleClassDependency_PreservesDependency(
2020
originalGraph.Nodes["B"] = classB;
2121

2222
var searchGraph = SearchGraphBuilder.BuildSearchGraph(originalGraph);
23-
var detailedGraph = CodeGraphBuilder.GenerateDetailedCodeGraph(searchGraph, originalGraph);
23+
var detailedGraph = CodeGraphBuilder.GenerateDetailedCodeGraph(searchGraph.Vertices, originalGraph);
2424

2525
Assert.AreEqual(2, detailedGraph.Nodes.Count);
2626
Assert.IsTrue(detailedGraph.Nodes.ContainsKey("A"));
@@ -47,7 +47,7 @@ public void GenerateDetailedCodeGraph_MethodDependency_PreservesMethodLevelDepen
4747
originalGraph.Nodes["B.M"] = methodB;
4848

4949
var searchGraph = SearchGraphBuilder.BuildSearchGraph(originalGraph);
50-
var detailedGraph = CodeGraphBuilder.GenerateDetailedCodeGraph(searchGraph, originalGraph);
50+
var detailedGraph = CodeGraphBuilder.GenerateDetailedCodeGraph(searchGraph.Vertices, originalGraph);
5151

5252
Assert.AreEqual(4, detailedGraph.Nodes.Count);
5353
Assert.IsTrue(detailedGraph.Nodes.ContainsKey("A"));
@@ -79,7 +79,7 @@ public void GenerateDetailedCodeGraph_MultipleDependencies_PreservesAllDependenc
7979
originalGraph.Nodes["B.M"] = methodB;
8080

8181
var searchGraph = SearchGraphBuilder.BuildSearchGraph(originalGraph);
82-
var detailedGraph = CodeGraphBuilder.GenerateDetailedCodeGraph(searchGraph, originalGraph);
82+
var detailedGraph = CodeGraphBuilder.GenerateDetailedCodeGraph(searchGraph.Vertices, originalGraph);
8383

8484
Assert.AreEqual(5, detailedGraph.Nodes.Count);
8585
Assert.AreEqual(1, detailedGraph.Nodes["A.M1"].Dependencies.Count);

CodeParserTests/TarjanTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using CodeParser.Analysis.Shared;
1+
using CodeParser.Analysis.Cycles;
2+
using CodeParser.Analysis.Shared;
3+
using GraphLib.Algorithms.StronglyConnectedComponents;
24

35
namespace CodeParserTests;
46

@@ -18,7 +20,7 @@ public void FindStronglyConnectedComponents_SimpleCircularDependency_ReturnsSing
1820

1921
var graph = new List<SearchNode> { nodeA, nodeB, nodeC };
2022

21-
var sccs = Tarjan.FindStronglyConnectedComponents(graph);
23+
var sccs = Tarjan.FindStronglyConnectedComponents(new SearchGraph(graph));
2224

2325
Assert.AreEqual(1, sccs.Count);
2426
Assert.AreEqual(3, sccs[0].Vertices.Count);

Contracts/Graph/CodeGraph.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
namespace Contracts.Graph;
1+
using GraphLib.Contracts;
2+
using System.Reflection.Metadata.Ecma335;
23

3-
public class CodeGraph
4+
namespace Contracts.Graph;
5+
6+
public class CodeGraph : IGraphRepresentation<CodeElement>
47
{
58
public Dictionary<string, CodeElement> Nodes = new();
69

10+
public uint VertexCount => (uint)Nodes.Count();
11+
712
public CodeElement? TryGetCodeElement(string? id)
813
{
914
if (string.IsNullOrEmpty(id))
@@ -111,4 +116,24 @@ public IEnumerable<Dependency> GetAllDependencies()
111116
{
112117
return Nodes.Values.SelectMany(n => n.Dependencies).ToList();
113118
}
119+
120+
public IReadOnlyCollection<CodeElement> GetNeighbors(CodeElement vertex)
121+
{
122+
return vertex.Dependencies.Select(d => Nodes[d.TargetId]).ToList();
123+
}
124+
125+
public bool IsVertex(CodeElement vertex)
126+
{
127+
return Nodes.ContainsKey(vertex.Id);
128+
}
129+
130+
public bool IsEdge(CodeElement source, CodeElement target)
131+
{
132+
return Nodes[source.Id].Dependencies.Any(d => d.TargetId == target.Id);
133+
}
134+
135+
public IReadOnlyCollection<CodeElement> GetVertices()
136+
{
137+
return Nodes.Values;
138+
}
114139
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace GraphLib.Contracts;
2+
3+
public class EdgeProperties
4+
{
5+
public double Weight { get; set; } = 1;
6+
7+
public static EdgeProperties ForWeight(double weight)
8+
{
9+
var edgeProperties = new EdgeProperties
10+
{
11+
Weight = weight
12+
};
13+
return edgeProperties;
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace GraphLib.Contracts;
2+
3+
public interface IGraphRepresentation<TVertex>
4+
{
5+
uint VertexCount { get; }
6+
IReadOnlyCollection<TVertex> GetNeighbors(TVertex vertex);
7+
bool IsVertex(TVertex vertex);
8+
bool IsEdge(TVertex source, TVertex target);
9+
IReadOnlyCollection<TVertex> GetVertices();
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace GraphLib.Contracts;
2+
3+
public interface IGraphRepresentationWidthEdgeProperties<TVertex> : IGraphRepresentation<TVertex>
4+
{
5+
EdgeProperties GetEdgeProperties(TVertex source, TVertex target);
6+
7+
IGraphRepresentationWidthEdgeProperties<TVertex> Transpose();
8+
}

0 commit comments

Comments
 (0)