diff --git a/Directory.Packages.props b/Directory.Packages.props index 28c565c..a73d9bd 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*");