From 180ee9dc1f08454bcc7f43c6fd3ee62cacc99be8 Mon Sep 17 00:00:00 2001 From: 0xsatoshi99 <0xsatoshi99@users.noreply.github.com> Date: Thu, 13 Nov 2025 03:12:28 +0100 Subject: [PATCH 1/3] Add Bipartite Graph Check algorithm Checks if a graph can be 2-colored (bipartite). A bipartite graph has vertices divisible into two independent sets. Features: - IsBipartite() - BFS-based check - IsBipartiteDfs() - DFS-based check - GetPartitions() - Returns the two sets - O(V + E) time complexity - Works with disconnected graphs Tests (20 test cases): - Empty, single, and two-vertex graphs - Triangles (odd cycles) and squares (even cycles) - Complete bipartite graphs K(m,n) - Disconnected components - Star graphs and pentagons - Large even/odd cycles - Edge cases and validation Use Cases: - Job assignment problems - Network flow modeling - Conflict detection - Graph coloring problems Files: - Algorithms/Graph/BipartiteGraph.cs (234 lines) - Algorithms.Tests/Graph/BipartiteGraphTests.cs (407 lines) --- Algorithms.Tests/Graph/BipartiteGraphTests.cs | 425 ++++++++++++++++++ Algorithms/Graph/BipartiteGraph.cs | 233 ++++++++++ 2 files changed, 658 insertions(+) create mode 100644 Algorithms.Tests/Graph/BipartiteGraphTests.cs create mode 100644 Algorithms/Graph/BipartiteGraph.cs diff --git a/Algorithms.Tests/Graph/BipartiteGraphTests.cs b/Algorithms.Tests/Graph/BipartiteGraphTests.cs new file mode 100644 index 00000000..ce038ed3 --- /dev/null +++ b/Algorithms.Tests/Graph/BipartiteGraphTests.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Algorithms.Graph; +using FluentAssertions; +using NUnit.Framework; + +namespace Algorithms.Tests.Graph; + +public class BipartiteGraphTests +{ + [Test] + public void IsBipartite_EmptyGraph_ReturnsTrue() + { + // Arrange + var vertices = Array.Empty(); + IEnumerable GetNeighbors(string v) => Array.Empty(); + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_SingleVertex_ReturnsTrue() + { + // Arrange + var vertices = new[] { "A" }; + IEnumerable GetNeighbors(string v) => Array.Empty(); + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_TwoVerticesConnected_ReturnsTrue() + { + // Arrange: A - B + var vertices = new[] { "A", "B" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B" }, + "B" => new[] { "A" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_Triangle_ReturnsFalse() + { + // Arrange: A - B - C - A (odd cycle) + var vertices = new[] { "A", "B", "C" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B", "C" }, + "B" => new[] { "A", "C" }, + "C" => new[] { "A", "B" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeFalse(); + } + + [Test] + public void IsBipartite_Square_ReturnsTrue() + { + // Arrange: A - B - C - D - A (even cycle) + var vertices = new[] { "A", "B", "C", "D" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B", "D" }, + "B" => new[] { "A", "C" }, + "C" => new[] { "B", "D" }, + "D" => new[] { "A", "C" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_CompleteBipartiteK23_ReturnsTrue() + { + // Arrange: Complete bipartite K(2,3) + var vertices = new[] { "A", "B", "X", "Y", "Z" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "X", "Y", "Z" }, + "B" => new[] { "X", "Y", "Z" }, + "X" => new[] { "A", "B" }, + "Y" => new[] { "A", "B" }, + "Z" => new[] { "A", "B" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_DisconnectedBipartiteComponents_ReturnsTrue() + { + // Arrange: (A-B) and (C-D) + var vertices = new[] { "A", "B", "C", "D" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B" }, + "B" => new[] { "A" }, + "C" => new[] { "D" }, + "D" => new[] { "C" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_DisconnectedWithOddCycle_ReturnsFalse() + { + // Arrange: (A-B) and (C-D-E-C triangle) + var vertices = new[] { "A", "B", "C", "D", "E" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B" }, + "B" => new[] { "A" }, + "C" => new[] { "D", "E" }, + "D" => new[] { "C", "E" }, + "E" => new[] { "C", "D" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeFalse(); + } + + [Test] + public void IsBipartite_StarGraph_ReturnsTrue() + { + // Arrange: Star with center A + var vertices = new[] { "A", "B", "C", "D" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B", "C", "D" }, + "B" => new[] { "A" }, + "C" => new[] { "A" }, + "D" => new[] { "A" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_Pentagon_ReturnsFalse() + { + // Arrange: Pentagon (5-cycle) + var vertices = new[] { 1, 2, 3, 4, 5 }; + IEnumerable GetNeighbors(int v) => v switch + { + 1 => new[] { 2, 5 }, + 2 => new[] { 1, 3 }, + 3 => new[] { 2, 4 }, + 4 => new[] { 3, 5 }, + 5 => new[] { 1, 4 }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeFalse(); + } + + [Test] + public void IsBipartite_NullVertices_ThrowsArgumentNullException() + { + // Act + Action act = () => BipartiteGraph.IsBipartite(null!, v => Array.Empty()); + + // Assert + act.Should().Throw().WithParameterName("vertices"); + } + + [Test] + public void IsBipartite_NullGetNeighbors_ThrowsArgumentNullException() + { + // Arrange + var vertices = new[] { "A" }; + + // Act + Action act = () => BipartiteGraph.IsBipartite(vertices, null!); + + // Assert + act.Should().Throw().WithParameterName("getNeighbors"); + } + + [Test] + public void GetPartitions_BipartiteGraph_ReturnsCorrectSets() + { + // Arrange: A - B - C - D (chain) + var vertices = new[] { "A", "B", "C", "D" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B" }, + "B" => new[] { "A", "C" }, + "C" => new[] { "B", "D" }, + "D" => new[] { "C" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); + + // Assert + result.Should().NotBeNull(); + result!.Value.SetA.Should().Contain(new[] { "A", "C" }); + result.Value.SetB.Should().Contain(new[] { "B", "D" }); + } + + [Test] + public void GetPartitions_NonBipartiteGraph_ReturnsNull() + { + // Arrange: Triangle + var vertices = new[] { "A", "B", "C" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B", "C" }, + "B" => new[] { "A", "C" }, + "C" => new[] { "A", "B" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); + + // Assert + result.Should().BeNull(); + } + + [Test] + public void GetPartitions_EmptyGraph_ReturnsEmptySets() + { + // Arrange + var vertices = Array.Empty(); + IEnumerable GetNeighbors(string v) => Array.Empty(); + + // Act + var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); + + // Assert + result.Should().NotBeNull(); + result!.Value.SetA.Should().BeEmpty(); + result.Value.SetB.Should().BeEmpty(); + } + + [Test] + public void IsBipartiteDfs_BipartiteGraph_ReturnsTrue() + { + // Arrange: Square + var vertices = new[] { "A", "B", "C", "D" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B", "D" }, + "B" => new[] { "A", "C" }, + "C" => new[] { "B", "D" }, + "D" => new[] { "A", "C" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartiteDfs(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartiteDfs_NonBipartiteGraph_ReturnsFalse() + { + // Arrange: Triangle + var vertices = new[] { "A", "B", "C" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "B", "C" }, + "B" => new[] { "A", "C" }, + "C" => new[] { "A", "B" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.IsBipartiteDfs(vertices, GetNeighbors); + + // Assert + result.Should().BeFalse(); + } + + [Test] + public void IsBipartite_LargeEvenCycle_ReturnsTrue() + { + // Arrange: Large even cycle (100 vertices) + var vertices = Enumerable.Range(0, 100).ToArray(); + IEnumerable GetNeighbors(int v) + { + var neighbors = new List(); + if (v > 0) + { + neighbors.Add(v - 1); + } + else + { + neighbors.Add(99); // Close the cycle + } + + if (v < 99) + { + neighbors.Add(v + 1); + } + else + { + neighbors.Add(0); // Close the cycle + } + + return neighbors; + } + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeTrue(); + } + + [Test] + public void IsBipartite_LargeOddCycle_ReturnsFalse() + { + // Arrange: Large odd cycle (101 vertices) + var vertices = Enumerable.Range(0, 101).ToArray(); + IEnumerable GetNeighbors(int v) + { + var neighbors = new List(); + if (v > 0) + { + neighbors.Add(v - 1); + } + else + { + neighbors.Add(100); + } + + if (v < 100) + { + neighbors.Add(v + 1); + } + else + { + neighbors.Add(0); + } + + return neighbors; + } + + // Act + var result = BipartiteGraph.IsBipartite(vertices, GetNeighbors); + + // Assert + result.Should().BeFalse(); + } + + [Test] + public void GetPartitions_CompleteBipartite_ReturnsCorrectSets() + { + // Arrange: K(2,2) + var vertices = new[] { "A", "B", "X", "Y" }; + IEnumerable GetNeighbors(string v) => v switch + { + "A" => new[] { "X", "Y" }, + "B" => new[] { "X", "Y" }, + "X" => new[] { "A", "B" }, + "Y" => new[] { "A", "B" }, + _ => Array.Empty(), + }; + + // Act + var result = BipartiteGraph.GetPartitions(vertices, GetNeighbors); + + // Assert + result.Should().NotBeNull(); + result!.Value.SetA.Should().HaveCount(2); + result.Value.SetB.Should().HaveCount(2); + } +} diff --git a/Algorithms/Graph/BipartiteGraph.cs b/Algorithms/Graph/BipartiteGraph.cs new file mode 100644 index 00000000..1b00323a --- /dev/null +++ b/Algorithms/Graph/BipartiteGraph.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Algorithms.Graph; + +/// +/// Checks if a graph is bipartite (2-colorable). +/// A bipartite graph can be divided into two independent sets where no two vertices +/// within the same set are adjacent. +/// +public static class BipartiteGraph +{ + /// + /// Checks if a graph is bipartite using BFS-based coloring. + /// + /// Type of vertex. + /// All vertices in the graph. + /// Function to get neighbors of a vertex. + /// True if graph is bipartite, false otherwise. + public static bool IsBipartite( + IEnumerable vertices, + Func> getNeighbors) where T : notnull + { + if (vertices == null) + { + throw new ArgumentNullException(nameof(vertices)); + } + + if (getNeighbors == null) + { + throw new ArgumentNullException(nameof(getNeighbors)); + } + + var vertexList = vertices.ToList(); + if (vertexList.Count == 0) + { + return true; // Empty graph is bipartite + } + + var colors = new Dictionary(); + + // Check each connected component + foreach (var start in vertexList) + { + if (colors.ContainsKey(start)) + { + continue; // Already colored + } + + if (!BfsColor(start, colors, getNeighbors)) + { + return false; + } + } + + return true; + } + + /// + /// Gets the two partitions of a bipartite graph. + /// + /// Type of vertex. + /// All vertices in the graph. + /// Function to get neighbors of a vertex. + /// Tuple of two sets representing the partitions, or null if not bipartite. + public static (HashSet SetA, HashSet SetB)? GetPartitions( + IEnumerable vertices, + Func> getNeighbors) where T : notnull + { + if (vertices == null) + { + throw new ArgumentNullException(nameof(vertices)); + } + + if (getNeighbors == null) + { + throw new ArgumentNullException(nameof(getNeighbors)); + } + + var vertexList = vertices.ToList(); + if (vertexList.Count == 0) + { + return (new HashSet(), new HashSet()); + } + + var colors = new Dictionary(); + + // Color all components + foreach (var start in vertexList) + { + if (colors.ContainsKey(start)) + { + continue; + } + + if (!BfsColor(start, colors, getNeighbors)) + { + return null; // Not bipartite + } + } + + // Split into two sets based on color + var setA = new HashSet(); + var setB = new HashSet(); + + foreach (var vertex in vertexList) + { + if (colors[vertex] == 0) + { + setA.Add(vertex); + } + else + { + setB.Add(vertex); + } + } + + return (setA, setB); + } + + /// + /// Checks if a graph is bipartite using DFS-based coloring. + /// + /// Type of vertex. + /// All vertices in the graph. + /// Function to get neighbors of a vertex. + /// True if graph is bipartite, false otherwise. + public static bool IsBipartiteDfs( + IEnumerable vertices, + Func> getNeighbors) where T : notnull + { + if (vertices == null) + { + throw new ArgumentNullException(nameof(vertices)); + } + + if (getNeighbors == null) + { + throw new ArgumentNullException(nameof(getNeighbors)); + } + + var vertexList = vertices.ToList(); + if (vertexList.Count == 0) + { + return true; + } + + var colors = new Dictionary(); + + foreach (var start in vertexList) + { + if (colors.ContainsKey(start)) + { + continue; + } + + if (!DfsColor(start, 0, colors, getNeighbors)) + { + return false; + } + } + + return true; + } + + private static bool BfsColor( + T start, + Dictionary colors, + Func> getNeighbors) where T : notnull + { + var queue = new Queue(); + queue.Enqueue(start); + colors[start] = 0; + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + var currentColor = colors[current]; + var nextColor = 1 - currentColor; + + foreach (var neighbor in getNeighbors(current)) + { + if (!colors.ContainsKey(neighbor)) + { + colors[neighbor] = nextColor; + queue.Enqueue(neighbor); + } + else if (colors[neighbor] == currentColor) + { + return false; // Same color as current - not bipartite + } + else + { + // Different color - valid + } + } + } + + return true; + } + + private static bool DfsColor( + T vertex, + int color, + Dictionary colors, + Func> getNeighbors) where T : notnull + { + colors[vertex] = color; + var nextColor = 1 - color; + + foreach (var neighbor in getNeighbors(vertex)) + { + if (!colors.ContainsKey(neighbor)) + { + if (!DfsColor(neighbor, nextColor, colors, getNeighbors)) + { + return false; + } + } + else if (colors[neighbor] == color) + { + return false; // Same color - not bipartite + } + else + { + // Different color - valid + } + } + + return true; + } +} From 2e6d830eb5384e38d42f001f98f7ff9ab663f8b4 Mon Sep 17 00:00:00 2001 From: 0xsatoshi99 <0xsatoshi99@users.noreply.github.com> Date: Thu, 13 Nov 2025 03:36:14 +0100 Subject: [PATCH 2/3] Fix Codacy issues: Use ternary operators Replace if-else blocks with ternary operators for cleaner code --- Algorithms.Tests/Graph/BipartiteGraphTests.cs | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/Algorithms.Tests/Graph/BipartiteGraphTests.cs b/Algorithms.Tests/Graph/BipartiteGraphTests.cs index ce038ed3..9a3b9126 100644 --- a/Algorithms.Tests/Graph/BipartiteGraphTests.cs +++ b/Algorithms.Tests/Graph/BipartiteGraphTests.cs @@ -335,24 +335,11 @@ public void IsBipartite_LargeEvenCycle_ReturnsTrue() var vertices = Enumerable.Range(0, 100).ToArray(); IEnumerable GetNeighbors(int v) { - var neighbors = new List(); - if (v > 0) + var neighbors = new List { - neighbors.Add(v - 1); - } - else - { - neighbors.Add(99); // Close the cycle - } - - if (v < 99) - { - neighbors.Add(v + 1); - } - else - { - neighbors.Add(0); // Close the cycle - } + v > 0 ? v - 1 : 99, // Previous or close cycle + v < 99 ? v + 1 : 0, // Next or close cycle + }; return neighbors; } @@ -371,24 +358,11 @@ public void IsBipartite_LargeOddCycle_ReturnsFalse() var vertices = Enumerable.Range(0, 101).ToArray(); IEnumerable GetNeighbors(int v) { - var neighbors = new List(); - if (v > 0) - { - neighbors.Add(v - 1); - } - else - { - neighbors.Add(100); - } - - if (v < 100) - { - neighbors.Add(v + 1); - } - else + var neighbors = new List { - neighbors.Add(0); - } + v > 0 ? v - 1 : 100, // Previous or close cycle + v < 100 ? v + 1 : 0, // Next or close cycle + }; return neighbors; } From 8822e48d7d1c225ef6db99d23031300f95ff298b Mon Sep 17 00:00:00 2001 From: 0xsatoshi99 <0xsatoshi99@users.noreply.github.com> Date: Thu, 13 Nov 2025 03:43:02 +0100 Subject: [PATCH 3/3] Add null parameter tests for better coverage Add validation tests for GetPartitions and IsBipartiteDfs methods --- Algorithms.Tests/Graph/BipartiteGraphTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Algorithms.Tests/Graph/BipartiteGraphTests.cs b/Algorithms.Tests/Graph/BipartiteGraphTests.cs index 9a3b9126..c4459ec5 100644 --- a/Algorithms.Tests/Graph/BipartiteGraphTests.cs +++ b/Algorithms.Tests/Graph/BipartiteGraphTests.cs @@ -287,6 +287,29 @@ public void GetPartitions_EmptyGraph_ReturnsEmptySets() result.Value.SetB.Should().BeEmpty(); } + [Test] + public void GetPartitions_NullVertices_ThrowsArgumentNullException() + { + // Act + Action act = () => BipartiteGraph.GetPartitions(null!, v => Array.Empty()); + + // Assert + act.Should().Throw().WithParameterName("vertices"); + } + + [Test] + public void GetPartitions_NullGetNeighbors_ThrowsArgumentNullException() + { + // Arrange + var vertices = new[] { "A" }; + + // Act + Action act = () => BipartiteGraph.GetPartitions(vertices, null!); + + // Assert + act.Should().Throw().WithParameterName("getNeighbors"); + } + [Test] public void IsBipartiteDfs_BipartiteGraph_ReturnsTrue() { @@ -328,6 +351,29 @@ public void IsBipartiteDfs_NonBipartiteGraph_ReturnsFalse() result.Should().BeFalse(); } + [Test] + public void IsBipartiteDfs_NullVertices_ThrowsArgumentNullException() + { + // Act + Action act = () => BipartiteGraph.IsBipartiteDfs(null!, v => Array.Empty()); + + // Assert + act.Should().Throw().WithParameterName("vertices"); + } + + [Test] + public void IsBipartiteDfs_NullGetNeighbors_ThrowsArgumentNullException() + { + // Arrange + var vertices = new[] { "A" }; + + // Act + Action act = () => BipartiteGraph.IsBipartiteDfs(vertices, null!); + + // Assert + act.Should().Throw().WithParameterName("getNeighbors"); + } + [Test] public void IsBipartite_LargeEvenCycle_ReturnsTrue() {