From 5454f469b7f52da8cf1182ba402881865eaecaa9 Mon Sep 17 00:00:00 2001 From: 1fisedi <51143882+1fisedi@users.noreply.github.com> Date: Sun, 3 Jan 2021 22:59:55 -0500 Subject: [PATCH 1/5] Added a solution to the nxn queen problem. --- .../NQueenProblem/NQueenProblemTests.cs | 57 +++++++++++ .../Problems/NQueenProblem/NQueenProblem.cs | 99 +++++++++++++++++++ README.md | 1 + 3 files changed, 157 insertions(+) create mode 100644 Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs create mode 100644 Algorithms/Problems/NQueenProblem/NQueenProblem.cs diff --git a/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs b/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs new file mode 100644 index 000000000..ffb9f36f3 --- /dev/null +++ b/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs @@ -0,0 +1,57 @@ +using System; + +using Algorithms.Problems; + +using NUnit.Framework; + +namespace Algorithms.Tests.Problems +{ + public static class NQueenProblemTests + { + [Test] + public static void SolvesCorrectly() + { + // Arrange + const int n = 8; + const int startCol = 0; + + var board = new int[n, n]; + + // Act + var result = NQueenProblem.BacktrackSolve(board, n, startCol); + + // Assert + Assert.True(result); + } + + [Test] + [TestCase(8, 15)] + [TestCase(5, 7)] + [TestCase(4, -1)] + public static void OutOfChessboardThrowsException(int n, int startCol) + { + // Arrange + var board = new int[n, n]; + + // Act + void Act() => NQueenProblem.BacktrackSolve(board, n, startCol); + + // Assert + _ = Assert.Throws(Act); + } + + [Test] + [TestCase(2)] + [TestCase(3)] + public static void UnsolvableSolutionThrowsError(int dim) + { + // Arrange + + // Act + void Act() => NQueenProblem.BacktrackSolve(new int[dim, dim], dim, 0); + + // Assert + _ = Assert.Throws(Act); + } + } +} diff --git a/Algorithms/Problems/NQueenProblem/NQueenProblem.cs b/Algorithms/Problems/NQueenProblem/NQueenProblem.cs new file mode 100644 index 000000000..63a7ff182 --- /dev/null +++ b/Algorithms/Problems/NQueenProblem/NQueenProblem.cs @@ -0,0 +1,99 @@ +using System; +using System.Linq; + +namespace Algorithms.Problems +{ + public static class NQueenProblem + { + /// + /// Solves N-Queen Problem given a n dimension chessboard and using backtracking with recursion algorithm. + /// If we find a dead-end within or current solution we go back and try another position for queen. + /// + /// initial board. + /// chessboard dimensions. + /// starting column. + /// Matrix solved. + public static bool BacktrackSolve(int[,] board, int n, int col) + { + if (n == 2 || n == 3) + { + // To return an error on known solution exceptions. + throw new ArgumentException("These dimensions do not have a solution."); + } + + if (col < 0 || col > n + 1) + { + throw new ArgumentException("Out-of-the-board exception."); + } + + // To check whether we have placed any queen on the board. + if (col >= n) + { + return board.Cast().ToList().Contains(1); + } + + // To start placing queens on possible spaces within the board. + for (var i = 0; i < n; i++) + { + if (CanPlace(board, i, col)) + { + board[i, col] = 1; + + if (BacktrackSolve(board, n, col + 1)) + { + return true; + } + + // if hitted a dead-end do backtracking. + board[i, col] = 0; + } + } + + return false; + } + + /// + /// Checks whether current queen can be placed in current position, + /// outside attacking range of another queen. + /// + /// Source board. + /// Row coordinate. + /// Col coordinate. + /// true if queen can be placed in given chessboard coordinates; false otherwise. + private static bool CanPlace(int[,] board, int row, int col) + { + int i; + int j; + + // To check whether there are any queens on current row. + for (i = 0; i < col; i++) + { + if (board[row, i] == 1) + { + return false; + } + } + + // To check diagonal attack top-left range. + for (i = row, j = col; i >= 0 && j >= 0; i--, j--) + { + if (board[i, j] == 1) + { + return false; + } + } + + // To check diagonal attack bottom-left range. + for (i = row, j = col; j >= 0 && i < board.GetLength(0); i++, j--) + { + if (board[i, j] == 1) + { + return false; + } + } + + // Return true if it can use position. + return true; + } + } +} diff --git a/README.md b/README.md index a7b08d1d0..fd1701a9d 100755 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ This repository contains algorithms and data structures implemented in C# for ed * [Problems](./Algorithms/Problems/) * [Stable Marriage](./Algorithms/Problems/StableMarriage) * [Gale-Shapley](./Algorithms/Problems/StableMarriage/GaleShapley.cs) + * [N-Queens Problem](./Algorithms/Problems/NQueenProblem/NQueenProblem.cs) * [Data Structures](./DataStructures/) * [Bit Array](./DataStructures/BitArray.cs) From fd287c559f38d7976e61d11f8598a31134569789 Mon Sep 17 00:00:00 2001 From: 1fisedi <51143882+1fisedi@users.noreply.github.com> Date: Mon, 4 Jan 2021 01:47:19 -0500 Subject: [PATCH 2/5] queen tests fix unused var fix. --- Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs b/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs index ffb9f36f3..8c81951e8 100644 --- a/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs +++ b/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs @@ -31,10 +31,9 @@ public static void SolvesCorrectly() public static void OutOfChessboardThrowsException(int n, int startCol) { // Arrange - var board = new int[n, n]; // Act - void Act() => NQueenProblem.BacktrackSolve(board, n, startCol); + void Act() => NQueenProblem.BacktrackSolve(new int[n, n], n, startCol); // Assert _ = Assert.Throws(Act); From 8f7c752ea3a11bf24fdeaf7c3984f56dbb7ada28 Mon Sep 17 00:00:00 2001 From: Andrii Siriak Date: Tue, 26 Jan 2021 00:07:34 +0200 Subject: [PATCH 3/5] Apply code review suggestions --- .../NQueenProblem/NQueenProblemTests.cs | 56 --------- .../NQueens/BacktrackingNQueensSolverTests.cs | 34 ++++++ .../Problems/NQueenProblem/NQueenProblem.cs | 99 ---------------- .../NQueens/BacktrackingNQueensSolver.cs | 108 ++++++++++++++++++ README.md | 3 +- 5 files changed, 144 insertions(+), 156 deletions(-) delete mode 100644 Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs create mode 100644 Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs delete mode 100644 Algorithms/Problems/NQueenProblem/NQueenProblem.cs create mode 100644 Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs diff --git a/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs b/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs deleted file mode 100644 index 8c81951e8..000000000 --- a/Algorithms.Tests/Problems/NQueenProblem/NQueenProblemTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; - -using Algorithms.Problems; - -using NUnit.Framework; - -namespace Algorithms.Tests.Problems -{ - public static class NQueenProblemTests - { - [Test] - public static void SolvesCorrectly() - { - // Arrange - const int n = 8; - const int startCol = 0; - - var board = new int[n, n]; - - // Act - var result = NQueenProblem.BacktrackSolve(board, n, startCol); - - // Assert - Assert.True(result); - } - - [Test] - [TestCase(8, 15)] - [TestCase(5, 7)] - [TestCase(4, -1)] - public static void OutOfChessboardThrowsException(int n, int startCol) - { - // Arrange - - // Act - void Act() => NQueenProblem.BacktrackSolve(new int[n, n], n, startCol); - - // Assert - _ = Assert.Throws(Act); - } - - [Test] - [TestCase(2)] - [TestCase(3)] - public static void UnsolvableSolutionThrowsError(int dim) - { - // Arrange - - // Act - void Act() => NQueenProblem.BacktrackSolve(new int[dim, dim], dim, 0); - - // Assert - _ = Assert.Throws(Act); - } - } -} diff --git a/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs b/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs new file mode 100644 index 000000000..05d844987 --- /dev/null +++ b/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs @@ -0,0 +1,34 @@ +using Algorithms.Problems.NQueens; + +using FluentAssertions; + +using NUnit.Framework; + +namespace Algorithms.Tests.Problems.NQueens +{ + public static class BacktrackingNQueensSolverTests + { + [TestCase(0, 0)] + [TestCase(1, 1)] + [TestCase(2, 0)] + [TestCase(3, 0)] + [TestCase(4, 2)] + [TestCase(5, 10)] + [TestCase(6, 4)] + [TestCase(7, 40)] + [TestCase(8, 92)] + [TestCase(8, 92)] + [TestCase(9, 352)] + [TestCase(10, 724)] + [TestCase(11, 2680)] + public static void SolvesCorrectly(int n, int expectedNumberOfSolutions) + { + // Arrange + // Act + var result = new BacktrackingNQueensSolver().BacktrackSolve(n); + + // Assert + result.Should().HaveCount(expectedNumberOfSolutions); + } + } +} diff --git a/Algorithms/Problems/NQueenProblem/NQueenProblem.cs b/Algorithms/Problems/NQueenProblem/NQueenProblem.cs deleted file mode 100644 index 63a7ff182..000000000 --- a/Algorithms/Problems/NQueenProblem/NQueenProblem.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Linq; - -namespace Algorithms.Problems -{ - public static class NQueenProblem - { - /// - /// Solves N-Queen Problem given a n dimension chessboard and using backtracking with recursion algorithm. - /// If we find a dead-end within or current solution we go back and try another position for queen. - /// - /// initial board. - /// chessboard dimensions. - /// starting column. - /// Matrix solved. - public static bool BacktrackSolve(int[,] board, int n, int col) - { - if (n == 2 || n == 3) - { - // To return an error on known solution exceptions. - throw new ArgumentException("These dimensions do not have a solution."); - } - - if (col < 0 || col > n + 1) - { - throw new ArgumentException("Out-of-the-board exception."); - } - - // To check whether we have placed any queen on the board. - if (col >= n) - { - return board.Cast().ToList().Contains(1); - } - - // To start placing queens on possible spaces within the board. - for (var i = 0; i < n; i++) - { - if (CanPlace(board, i, col)) - { - board[i, col] = 1; - - if (BacktrackSolve(board, n, col + 1)) - { - return true; - } - - // if hitted a dead-end do backtracking. - board[i, col] = 0; - } - } - - return false; - } - - /// - /// Checks whether current queen can be placed in current position, - /// outside attacking range of another queen. - /// - /// Source board. - /// Row coordinate. - /// Col coordinate. - /// true if queen can be placed in given chessboard coordinates; false otherwise. - private static bool CanPlace(int[,] board, int row, int col) - { - int i; - int j; - - // To check whether there are any queens on current row. - for (i = 0; i < col; i++) - { - if (board[row, i] == 1) - { - return false; - } - } - - // To check diagonal attack top-left range. - for (i = row, j = col; i >= 0 && j >= 0; i--, j--) - { - if (board[i, j] == 1) - { - return false; - } - } - - // To check diagonal attack bottom-left range. - for (i = row, j = col; j >= 0 && i < board.GetLength(0); i++, j--) - { - if (board[i, j] == 1) - { - return false; - } - } - - // Return true if it can use position. - return true; - } - } -} diff --git a/Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs b/Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs new file mode 100644 index 000000000..568012f12 --- /dev/null +++ b/Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; + +namespace Algorithms.Problems.NQueens +{ + public class BacktrackingNQueensSolver + { + /// + /// Solves N-Queen Problem given a n dimension chessboard and using backtracking with recursion algorithm. + /// If we find a dead-end within or current solution we go back and try another position for queen. + /// + /// Number of rows. + /// All solutions. + public IEnumerable BacktrackSolve(int n) + { + if (n < 0) + { + throw new ArgumentException(nameof(n)); + } + + return BacktrackSolve(new bool[n, n], 0); + } + + private static IEnumerable BacktrackSolve(bool[,] board, int col) + { + var solutions = col < board.GetLength(0) - 1 + ? HandleIntermediateColumn(board, col) + : HandleLastColumn(board); + return solutions; + } + + private static IEnumerable HandleIntermediateColumn(bool[,] board, int col) + { + // To start placing queens on possible spaces within the board. + for (var i = 0; i < board.GetLength(0); i++) + { + if (CanPlace(board, i, col)) + { + board[i, col] = true; + + foreach (var solution in BacktrackSolve(board, col + 1)) + { + yield return solution; + } + + board[i, col] = false; + } + } + } + + private static IEnumerable HandleLastColumn(bool[,] board) + { + var n = board.GetLength(0); + for (var i = 0; i < n; i++) + { + if (CanPlace(board, i, n - 1)) + { + board[i, n - 1] = true; + + yield return (bool[,])board.Clone(); + + board[i, n - 1] = false; + } + } + } + + /// + /// Checks whether current queen can be placed in current position, + /// outside attacking range of another queen. + /// + /// Source board. + /// Row coordinate. + /// Col coordinate. + /// true if queen can be placed in given chessboard coordinates; false otherwise. + private static bool CanPlace(bool[,] board, int row, int col) + { + // To check whether there are any queens on current row. + for (var i = 0; i < col; i++) + { + if (board[row, i]) + { + return false; + } + } + + // To check diagonal attack top-left range. + for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) + { + if (board[i, j]) + { + return false; + } + } + + // To check diagonal attack bottom-left range. + for (int i = row + 1, j = col - 1; j >= 0 && i < board.GetLength(0); i++, j--) + { + if (board[i, j]) + { + return false; + } + } + + // Return true if it can use position. + return true; + } + } +} diff --git a/README.md b/README.md index fd1701a9d..c8cb49bc2 100755 --- a/README.md +++ b/README.md @@ -89,7 +89,8 @@ This repository contains algorithms and data structures implemented in C# for ed * [Problems](./Algorithms/Problems/) * [Stable Marriage](./Algorithms/Problems/StableMarriage) * [Gale-Shapley](./Algorithms/Problems/StableMarriage/GaleShapley.cs) - * [N-Queens Problem](./Algorithms/Problems/NQueenProblem/NQueenProblem.cs) + * [N-Queens](./Algorithms/Problems/NQueens) + * [Backtracking](./Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs) * [Data Structures](./DataStructures/) * [Bit Array](./DataStructures/BitArray.cs) From 1b9271bb7a549ec74c179d193f1114bac17dfb85 Mon Sep 17 00:00:00 2001 From: Andrii Siriak Date: Tue, 26 Jan 2021 09:19:44 +0200 Subject: [PATCH 4/5] Add more tests --- .../NQueens/BacktrackingNQueensSolverTests.cs | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs b/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs index 05d844987..9b45a4354 100644 --- a/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs +++ b/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs @@ -1,4 +1,5 @@ -using Algorithms.Problems.NQueens; +using System.Linq; +using Algorithms.Problems.NQueens; using FluentAssertions; @@ -21,14 +22,97 @@ public static class BacktrackingNQueensSolverTests [TestCase(9, 352)] [TestCase(10, 724)] [TestCase(11, 2680)] + [TestCase(12, 14200)] public static void SolvesCorrectly(int n, int expectedNumberOfSolutions) { // Arrange // Act - var result = new BacktrackingNQueensSolver().BacktrackSolve(n); + var result = new BacktrackingNQueensSolver().BacktrackSolve(n).ToList(); // Assert result.Should().HaveCount(expectedNumberOfSolutions); + foreach (var solution in result) + { + ValidateOneQueenPerRow(solution); + ValidateOneQueenPerColumn(solution); + ValidateOneQueenPerTopLeftBottomRightDiagonalLine(solution); + ValidateOneQueenPerBottomLeftTopRightDiagonalLine(solution); + } + } + private static void ValidateOneQueenPerRow(bool[,] solution) + { + for (var i = 0; i < solution.GetLength(1); i++) + { + var foundQueen = false; + for (var j = 0; j < solution.GetLength(0); j++) + { + foundQueen = ValidateCell(foundQueen, solution[j, i]); + } + } + } + + private static void ValidateOneQueenPerColumn(bool[,] solution) + { + for (var i = 0; i < solution.GetLength(0); i++) + { + var foundQueen = false; + for (var j = 0; j < solution.GetLength(1); j++) + { + foundQueen = ValidateCell(foundQueen, solution[i, j]); + } + } + } + + private static void ValidateOneQueenPerTopLeftBottomRightDiagonalLine(bool[,] solution) + { + for (var i = 0; i < solution.GetLength(0); i++) + { + var foundQueen = false; + for (var j = 0; i + j < solution.GetLength(1); j++) + { + foundQueen = ValidateCell(foundQueen, solution[i + j, i]); + } + } + + for (var i = 0; i < solution.GetLength(1); i++) + { + var foundQueen = false; + for (var j = 0; i + j < solution.GetLength(0); j++) + { + foundQueen = ValidateCell(foundQueen, solution[j, i + j]); + } + } + } + + private static void ValidateOneQueenPerBottomLeftTopRightDiagonalLine(bool[,] solution) + { + for (var i = 0; i < solution.GetLength(0); i++) + { + var foundQueen = false; + for (var j = 0; i - j >= 0; j++) + { + foundQueen = ValidateCell(foundQueen, solution[i - j, i]); + } + } + + for (var i = 0; i < solution.GetLength(1); i++) + { + var foundQueen = false; + for (var j = 0; i - j >= 0 && solution.GetLength(0) - j > 0; j++) + { + foundQueen = ValidateCell(foundQueen, solution[solution.GetLength(0) - j - 1, i - j]); + } + } + } + + static bool ValidateCell(bool foundQueen, bool currentCell) + { + if (foundQueen) + { + currentCell.Should().BeFalse(); + } + + return foundQueen || currentCell; } } } From 74db8a59fb3ed8736e47164be85142ec49ec0571 Mon Sep 17 00:00:00 2001 From: Andrii Siriak Date: Tue, 26 Jan 2021 09:31:26 +0200 Subject: [PATCH 5/5] Improve test coverage --- .../NQueens/BacktrackingNQueensSolverTests.cs | 14 +++++++++++++- .../Problems/StableMarriage/GaleShapleyTests.cs | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs b/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs index 9b45a4354..cb013656e 100644 --- a/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs +++ b/Algorithms.Tests/Problems/NQueens/BacktrackingNQueensSolverTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Algorithms.Problems.NQueens; using FluentAssertions; @@ -39,6 +40,17 @@ public static void SolvesCorrectly(int n, int expectedNumberOfSolutions) ValidateOneQueenPerBottomLeftTopRightDiagonalLine(solution); } } + + [Test] + public static void NCannotBeNegative() + { + var n = -1; + + Action act = () => new BacktrackingNQueensSolver().BacktrackSolve(n); + + act.Should().Throw(); + } + private static void ValidateOneQueenPerRow(bool[,] solution) { for (var i = 0; i < solution.GetLength(1); i++) diff --git a/Algorithms.Tests/Problems/StableMarriage/GaleShapleyTests.cs b/Algorithms.Tests/Problems/StableMarriage/GaleShapleyTests.cs index fea6f059c..f85ae9c89 100644 --- a/Algorithms.Tests/Problems/StableMarriage/GaleShapleyTests.cs +++ b/Algorithms.Tests/Problems/StableMarriage/GaleShapleyTests.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; +using Algorithms.Problems.StableMarriage; + using NUnit.Framework; -namespace Algorithms.Problems.StableMarriage +namespace Algorithms.Tests.Problems.StableMarriage { /// /// The stable marriage problem (also stable matching problem or SMP)