From b9fd43627d07843b37da4b15a7ec1719ee145c94 Mon Sep 17 00:00:00 2001 From: HandyS11 Date: Thu, 12 Mar 2026 12:39:36 +0100 Subject: [PATCH 1/2] chore: update package versions and improve Tarjan's algorithm implementation --- Directory.Packages.props | 16 +-- global.json | 2 +- .../Domain/Algorithms/TarjanSccAlgorithm.cs | 110 +++++++++++------- .../NullOutputConsoleTests.cs | 2 +- 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 28c565c..6c60db9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,15 +5,15 @@ - - - - - + + + + + - - - + + + diff --git a/global.json b/global.json index 4f01dc2..066c229 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.103", + "version": "10.0.200", "rollForward": "latestMajor", "allowPrerelease": true } diff --git a/src/ProjGraph.Lib.Core/Domain/Algorithms/TarjanSccAlgorithm.cs b/src/ProjGraph.Lib.Core/Domain/Algorithms/TarjanSccAlgorithm.cs index e3f9495..1877b9f 100644 --- a/src/ProjGraph.Lib.Core/Domain/Algorithms/TarjanSccAlgorithm.cs +++ b/src/ProjGraph.Lib.Core/Domain/Algorithms/TarjanSccAlgorithm.cs @@ -109,38 +109,8 @@ private static void StrongConnect( { var frame = dfsStack.Pop(); var node = frame.Node; - var neighborIdx = frame.NeighborIndex; - var neighbors = adjacencyList.TryGetValue(node, out var list) ? list : []; - - var pushed = false; - for (var i = neighborIdx; i < neighbors.Count; i++) - { - var w = neighbors[i]; - if (!context.Indices.TryGetValue(w, out var wIndex)) - { - // Save current frame (will resume at neighbor i+1 after w completes) - dfsStack.Push(new StackFrame(node, i + 1)); - - // Initialize w and push it - context.Indices[w] = context.Index; - context.Lowlink[w] = context.Index; - context.Index++; - context.Stack.Push(w); - context.OnStack.Add(w); - - dfsStack.Push(new StackFrame(w, 0)); - pushed = true; - break; - } - - if (context.OnStack.Contains(w)) - { - context.Lowlink[node] = Math.Min(context.Lowlink[node], wIndex); - } - } - - if (pushed) + if (TryPushNeighbor(node, frame.NeighborIndex, adjacencyList, dfsStack, context)) { continue; } @@ -148,16 +118,7 @@ private static void StrongConnect( // All neighbors processed — check for SCC root if (context.Lowlink[node] == context.Indices[node]) { - var component = new List(); - Guid w; - do - { - w = context.Stack.Pop(); - context.OnStack.Remove(w); - component.Add(w); - } while (w != node); - - context.Sccs.Add(component); + CollectSccComponent(node, context); } // Update parent's lowlink @@ -168,4 +129,71 @@ private static void StrongConnect( } } } + + /// + /// Iterates over the neighbors of starting at , + /// and pushes the first unvisited neighbor onto the DFS stack. + /// + /// The node whose neighbors to iterate. + /// The index into the neighbor list to resume iteration from. + /// The adjacency list representing the graph. + /// The explicit DFS stack used by the iterative algorithm. + /// The context of the Tarjan's algorithm execution. + /// if an unvisited neighbor was pushed; otherwise . + private static bool TryPushNeighbor( + Guid node, + int neighborIdx, + Dictionary> adjacencyList, + Stack dfsStack, + TarjanContext context) + { + var neighbors = adjacencyList.TryGetValue(node, out var list) ? list : []; + + for (var i = neighborIdx; i < neighbors.Count; i++) + { + var w = neighbors[i]; + if (!context.Indices.TryGetValue(w, out var wIndex)) + { + // Save current frame (will resume at neighbor i+1 after w completes) + dfsStack.Push(new StackFrame(node, i + 1)); + + // Initialize w and push it + context.Indices[w] = context.Index; + context.Lowlink[w] = context.Index; + context.Index++; + context.Stack.Push(w); + context.OnStack.Add(w); + + dfsStack.Push(new StackFrame(w, 0)); + return true; + } + + if (context.OnStack.Contains(w)) + { + context.Lowlink[node] = Math.Min(context.Lowlink[node], wIndex); + } + } + + return false; + } + + /// + /// Pops nodes from the Tarjan stack until is reached, + /// forming a strongly connected component and adding it to the results. + /// + /// The root node of the strongly connected component. + /// The context of the Tarjan's algorithm execution. + private static void CollectSccComponent(Guid node, TarjanContext context) + { + var component = new List(); + Guid w; + do + { + w = context.Stack.Pop(); + context.OnStack.Remove(w); + component.Add(w); + } while (w != node); + + context.Sccs.Add(component); + } } diff --git a/tests/ProjGraph.Tests.Unit.Core/NullOutputConsoleTests.cs b/tests/ProjGraph.Tests.Unit.Core/NullOutputConsoleTests.cs index b2e5ea3..11699d3 100644 --- a/tests/ProjGraph.Tests.Unit.Core/NullOutputConsoleTests.cs +++ b/tests/ProjGraph.Tests.Unit.Core/NullOutputConsoleTests.cs @@ -82,7 +82,7 @@ public async Task PromptSelectionAsync_ShouldReturnFirstChoice() [Fact] public async Task PromptSelectionAsync_EmptyChoices_ShouldThrowInvalidOperationException() { - var act = () => _sut.PromptSelectionAsync("Pick one", Array.Empty()); + var act = () => _sut.PromptSelectionAsync("Pick one", []); await act.Should().ThrowAsync() .WithMessage("*No choices available*"); From 652df9fc1876d9d30337dd214336387a36f9eeb9 Mon Sep 17 00:00:00 2001 From: Clergue Valentin Date: Thu, 12 Mar 2026 14:05:05 +0100 Subject: [PATCH 2/2] Update Directory.Packages.props Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 6c60db9..a73d9bd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,7 +10,7 @@ - +