From 818db7c1171d6bd6ea1fdc960f41c0b09547b468 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 5 Oct 2025 14:54:29 +0530 Subject: [PATCH 1/5] refactor: Enhance docs, code, add tests in `MinimaxAlgorithm` --- .../others/MiniMaxAlgorithm.java | 140 +++++++++++++----- .../others/MiniMaxAlgorithmTest.java | 126 ++++++++++++---- 2 files changed, 205 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java b/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java index e0b96570a4fe..28dc980034f3 100644 --- a/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java +++ b/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java @@ -4,54 +4,99 @@ import java.util.Random; /** - * MiniMax is an algorithm used int artificial intelligence and game theory for - * minimizing the possible loss for the worst case scenario. + * MiniMax is an algorithm used in artificial intelligence and game theory for + * minimizing the possible loss for the worst case scenario. It is commonly used + * in two-player turn-based games such as Tic-Tac-Toe, Chess, and Checkers. * - * See more (https://en.wikipedia.org/wiki/Minimax, - * https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-1-introduction/). + *

+ * The algorithm simulates all possible moves in a game tree and chooses the + * move that minimizes the maximum possible loss. The algorithm assumes both + * players play optimally. + * + *

+ * Time Complexity: O(b^d) where b is the branching factor and d is the depth + *

+ * Space Complexity: O(d) for the recursive call stack + * + *

+ * See more: + *

* * @author aitofi (https://github.com/aitorfi) */ -public class MiniMaxAlgorithm { +public final class MiniMaxAlgorithm { + + private static final Random RANDOM = new Random(); /** * Game tree represented as an int array containing scores. Each array - * element is a leaf node. + * element is a leaf node. The array length must be a power of 2. */ private int[] scores; + + /** + * The height of the game tree, calculated as log2(scores.length). + */ private int height; /** - * Initializes the scores with 8 random leaf nodes + * Initializes the MiniMaxAlgorithm with 8 random leaf nodes (2^3 = 8). + * Each score is a random integer between 1 and 99 inclusive. */ public MiniMaxAlgorithm() { - scores = getRandomScores(3, 99); - height = log2(scores.length); + this(getRandomScores(3, 99)); + } + + /** + * Initializes the MiniMaxAlgorithm with the provided scores. + * + * @param scores An array of scores representing leaf nodes. The length must be + * a power of 2. + * @throws IllegalArgumentException if the scores array length is not a power of + * 2 + */ + public MiniMaxAlgorithm(int[] scores) { + if (!isPowerOfTwo(scores.length)) { + throw new IllegalArgumentException("The number of scores must be a power of 2."); + } + this.scores = Arrays.copyOf(scores, scores.length); + this.height = log2(scores.length); } + /** + * Demonstrates the MiniMax algorithm with a random game tree. + * + * @param args Command line arguments (not used) + */ public static void main(String[] args) { - MiniMaxAlgorithm miniMaxAlgorith = new MiniMaxAlgorithm(); + MiniMaxAlgorithm miniMaxAlgorithm = new MiniMaxAlgorithm(); boolean isMaximizer = true; // Specifies the player that goes first. - boolean verbose = true; // True to show each players choices. int bestScore; - bestScore = miniMaxAlgorith.miniMax(0, isMaximizer, 0, verbose); + bestScore = miniMaxAlgorithm.miniMax(0, isMaximizer, 0, true); - if (verbose) { - System.out.println(); - } - - System.out.println(Arrays.toString(miniMaxAlgorith.getScores())); + System.out.println(); + System.out.println(Arrays.toString(miniMaxAlgorithm.getScores())); System.out.println("The best score for " + (isMaximizer ? "Maximizer" : "Minimizer") + " is " + bestScore); } /** * Returns the optimal score assuming that both players play their best. * - * @param depth Indicates how deep we are into the game tree. - * @param isMaximizer True if it is maximizers turn; otherwise false. - * @param index Index of the leaf node that is being evaluated. - * @param verbose True to show each players choices. + *

+ * This method recursively evaluates the game tree using the minimax algorithm. + * At each level, the maximizer tries to maximize the score while the minimizer + * tries to minimize it. + * + * @param depth The current depth in the game tree (0 at root). + * @param isMaximizer True if it is the maximizer's turn; false for minimizer. + * @param index Index of the current node in the game tree. + * @param verbose True to print each player's choice during evaluation. * @return The optimal score for the player that made the first move. */ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) { @@ -75,7 +120,7 @@ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) { } // Leaf nodes can be sequentially inspected by - // recurssively multiplying (0 * 2) and ((0 * 2) + 1): + // recursively multiplying (0 * 2) and ((0 * 2) + 1): // (0 x 2) = 0; ((0 x 2) + 1) = 1 // (1 x 2) = 2; ((1 x 2) + 1) = 3 // (2 x 2) = 4; ((2 x 2) + 1) = 5 ... @@ -87,46 +132,73 @@ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) { } /** - * Returns an array of random numbers which lenght is a power of 2. + * Returns an array of random numbers whose length is a power of 2. * - * @param size The power of 2 that will determine the lenght of the array. - * @param maxScore The maximum possible score. - * @return An array of random numbers. + * @param size The power of 2 that will determine the length of the array + * (array length = 2^size). + * @param maxScore The maximum possible score (scores will be between 1 and + * maxScore inclusive). + * @return An array of random numbers with length 2^size. */ public static int[] getRandomScores(int size, int maxScore) { int[] randomScores = new int[(int) Math.pow(2, size)]; - Random rand = new Random(); for (int i = 0; i < randomScores.length; i++) { - randomScores[i] = rand.nextInt(maxScore) + 1; + randomScores[i] = RANDOM.nextInt(maxScore) + 1; } return randomScores; } - // A utility function to find Log n in base 2 + /** + * Calculates the logarithm base 2 of a number. + * + * @param n The number to calculate log2 for (must be a power of 2). + * @return The log2 of n. + */ private int log2(int n) { return (n == 1) ? 0 : log2(n / 2) + 1; } - // A utility function to check if a number is a power of 2 + /** + * Checks if a number is a power of 2. + * + * @param n The number to check. + * @return True if n is a power of 2, false otherwise. + */ private boolean isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; } + /** + * Sets the scores array for the game tree. + * + * @param scores The array of scores. Length must be a power of 2. + * @throws IllegalArgumentException if the scores array length is not a power of + * 2 + */ public void setScores(int[] scores) { if (!isPowerOfTwo(scores.length)) { - System.out.println("The number of scores must be a power of 2."); - return; + throw new IllegalArgumentException("The number of scores must be a power of 2."); } - this.scores = scores; + this.scores = Arrays.copyOf(scores, scores.length); height = log2(this.scores.length); } + /** + * Returns a copy of the scores array. + * + * @return A copy of the scores array. + */ public int[] getScores() { - return scores; + return Arrays.copyOf(scores, scores.length); } + /** + * Returns the height of the game tree. + * + * @return The height of the game tree (log2 of the number of leaf nodes). + */ public int getHeight() { return height; } diff --git a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java index 821eb3f16029..a9e7185572f7 100644 --- a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java @@ -3,10 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -26,6 +28,11 @@ void setUp() { System.setOut(new PrintStream(outputStream)); } + @AfterEach + void tearDown() { + System.setOut(originalOut); + } + @Test void testConstructorCreatesValidScores() { // The default constructor should create scores array of length 8 (2^3) @@ -38,6 +45,34 @@ void testConstructorCreatesValidScores() { } } + @Test + void testConstructorWithValidScores() { + int[] validScores = {10, 20, 30, 40}; + MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(validScores); + + assertArrayEquals(validScores, customMiniMax.getScores()); + assertEquals(2, customMiniMax.getHeight()); // log2(4) = 2 + } + + @Test + void testConstructorWithInvalidScoresThrowsException() { + int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 + assertThrows(IllegalArgumentException.class, () -> new MiniMaxAlgorithm(invalidScores)); + } + + @Test + void testConstructorDoesNotModifyOriginalArray() { + int[] originalScores = {10, 20, 30, 40}; + int[] copyOfOriginal = {10, 20, 30, 40}; + MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(originalScores); + + // Modify the original array + originalScores[0] = 999; + + // Constructor should have made a copy, so internal state should be unchanged + assertArrayEquals(copyOfOriginal, customMiniMax.getScores()); + } + @Test void testSetScoresWithValidPowerOfTwo() { int[] validScores = {10, 20, 30, 40}; @@ -50,11 +85,7 @@ void testSetScoresWithValidPowerOfTwo() { @Test void testSetScoresWithInvalidLength() { int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 - miniMax.setScores(invalidScores); - - // Should print error message and not change the scores - String output = outputStream.toString(); - assertTrue(output.contains("The number of scores must be a power of 2.")); + assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores)); // Scores should remain unchanged (original length 8) assertEquals(8, miniMax.getScores().length); @@ -63,11 +94,7 @@ void testSetScoresWithInvalidLength() { @Test void testSetScoresWithZeroLength() { int[] emptyScores = {}; // Length 0 is not a power of 2 - miniMax.setScores(emptyScores); - - // Should print error message and not change the scores - String output = outputStream.toString(); - assertTrue(output.contains("The number of scores must be a power of 2.")); + assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyScores)); // Scores should remain unchanged (original length 8) assertEquals(8, miniMax.getScores().length); @@ -75,7 +102,8 @@ void testSetScoresWithZeroLength() { @Test void testSetScoresWithVariousInvalidLengths() { - // Test multiple invalid lengths to ensure isPowerOfTwo function is fully covered + // Test multiple invalid lengths to ensure isPowerOfTwo function is fully + // covered int[][] invalidScoreArrays = { {1, 2, 3, 4, 5}, // Length 5 {1, 2, 3, 4, 5, 6}, // Length 6 @@ -86,13 +114,7 @@ void testSetScoresWithVariousInvalidLengths() { }; for (int[] invalidScores : invalidScoreArrays) { - // Clear the output stream for each test - outputStream.reset(); - miniMax.setScores(invalidScores); - - // Should print error message for each invalid length - String output = outputStream.toString(); - assertTrue(output.contains("The number of scores must be a power of 2."), "Failed for array length: " + invalidScores.length); + assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores), "Failed for array length: " + invalidScores.length); } // Scores should remain unchanged (original length 8) @@ -139,7 +161,7 @@ void testMiniMaxWithLargerTree() { // Maximizer starts int result = miniMax.miniMax(0, true, 0, false); // Expected: max(min(max(5,6), max(7,4)), min(max(5,3), max(6,2))) - // = max(min(6, 7), min(5, 6)) = max(6, 5) = 6 + // = max(min(6, 7), min(5, 6)) = max(6, 5) = 6 assertEquals(6, result); } @@ -221,10 +243,6 @@ void testEdgeCaseWithNegativeScores() { assertEquals(-5, result); } - void tearDown() { - System.setOut(originalOut); - } - @Test void testSetScoresWithNegativeLength() { // This test ensures the first condition of isPowerOfTwo (n > 0) is tested @@ -233,11 +251,8 @@ void testSetScoresWithNegativeLength() { // Test with array length 0 (edge case for n > 0 condition) int[] emptyArray = new int[0]; - outputStream.reset(); - miniMax.setScores(emptyArray); + assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyArray)); - String output = outputStream.toString(); - assertTrue(output.contains("The number of scores must be a power of 2.")); assertEquals(8, miniMax.getScores().length); // Should remain unchanged } @@ -274,4 +289,61 @@ void testSetScoresValidEdgeCases() { assertEquals(expectedHeights[i], miniMax.getHeight(), "Height calculation failed for array length: " + validPowersOf2[i].length); } } + + @Test + void testGetScoresReturnsDefensiveCopy() { + int[] originalScores = {10, 20, 30, 40}; + miniMax.setScores(originalScores); + + // Get the scores and modify them + int[] retrievedScores = miniMax.getScores(); + retrievedScores[0] = 999; + + // Internal state should remain unchanged + assertEquals(10, miniMax.getScores()[0]); + } + + @Test + void testSetScoresCreatesDefensiveCopy() { + int[] originalScores = {10, 20, 30, 40}; + miniMax.setScores(originalScores); + + // Modify the original array after setting + originalScores[0] = 999; + + // Internal state should remain unchanged + assertEquals(10, miniMax.getScores()[0]); + } + + @Test + void testMiniMaxWithAllSameScores() { + int[] sameScores = {5, 5, 5, 5}; + miniMax.setScores(sameScores); + + // When all scores are the same, result should be that score + int result = miniMax.miniMax(0, true, 0, false); + assertEquals(5, result); + } + + @Test + void testMiniMaxAtDifferentDepths() { + int[] testScores = {3, 12, 8, 2, 14, 5, 2, 9}; + miniMax.setScores(testScores); + + // Test maximizer first + int result = miniMax.miniMax(0, true, 0, false); + // Expected: max(min(max(3,12), max(8,2)), min(max(14,5), max(2,9))) + // = max(min(12, 8), min(14, 9)) = max(8, 9) = 9 + assertEquals(9, result); + } + + @Test + void testMiniMaxWithMinIntAndMaxInt() { + int[] extremeScores = {Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1}; + miniMax.setScores(extremeScores); + + int result = miniMax.miniMax(0, true, 0, false); + // Expected: max(min(MIN, MAX), min(0, 1)) = max(MIN, 0) = 0 + assertEquals(0, result); + } } From 5015c29523461d0d8bdf1047b35652da8bc5aa3f Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 5 Oct 2025 15:27:32 +0530 Subject: [PATCH 2/5] Fix --- .../others/MiniMaxAlgorithmTest.java | 164 +++++++++--------- 1 file changed, 81 insertions(+), 83 deletions(-) diff --git a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java index a9e7185572f7..54e50d892c41 100644 --- a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java @@ -1,14 +1,9 @@ package com.thealgorithms.others; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,68 +31,68 @@ void tearDown() { @Test void testConstructorCreatesValidScores() { // The default constructor should create scores array of length 8 (2^3) - assertEquals(8, miniMax.getScores().length); - assertEquals(3, miniMax.getHeight()); + Assertions.assertEquals(8, miniMax.getScores().length); + Assertions.assertEquals(3, miniMax.getHeight()); // All scores should be positive (between 1 and 99) for (int score : miniMax.getScores()) { - assertTrue(score >= 1 && score <= 99); + Assertions.assertTrue(score >= 1 && score <= 99); } } @Test void testConstructorWithValidScores() { - int[] validScores = {10, 20, 30, 40}; + int[] validScores = { 10, 20, 30, 40 }; MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(validScores); - assertArrayEquals(validScores, customMiniMax.getScores()); - assertEquals(2, customMiniMax.getHeight()); // log2(4) = 2 + Assertions.assertArrayEquals(validScores, customMiniMax.getScores()); + Assertions.assertEquals(2, customMiniMax.getHeight()); // log2(4) = 2 } @Test void testConstructorWithInvalidScoresThrowsException() { - int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 - assertThrows(IllegalArgumentException.class, () -> new MiniMaxAlgorithm(invalidScores)); + int[] invalidScores = { 10, 20, 30 }; // Length 3 is not a power of 2 + Assertions.assertThrows(IllegalArgumentException.class, () -> new MiniMaxAlgorithm(invalidScores)); } @Test void testConstructorDoesNotModifyOriginalArray() { - int[] originalScores = {10, 20, 30, 40}; - int[] copyOfOriginal = {10, 20, 30, 40}; + int[] originalScores = { 10, 20, 30, 40 }; + int[] copyOfOriginal = { 10, 20, 30, 40 }; MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(originalScores); // Modify the original array originalScores[0] = 999; // Constructor should have made a copy, so internal state should be unchanged - assertArrayEquals(copyOfOriginal, customMiniMax.getScores()); + Assertions.assertArrayEquals(copyOfOriginal, customMiniMax.getScores()); } @Test void testSetScoresWithValidPowerOfTwo() { - int[] validScores = {10, 20, 30, 40}; + int[] validScores = { 10, 20, 30, 40 }; miniMax.setScores(validScores); - assertArrayEquals(validScores, miniMax.getScores()); - assertEquals(2, miniMax.getHeight()); // log2(4) = 2 + Assertions.assertArrayEquals(validScores, miniMax.getScores()); + Assertions.assertEquals(2, miniMax.getHeight()); // log2(4) = 2 } @Test void testSetScoresWithInvalidLength() { - int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 - assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores)); + int[] invalidScores = { 10, 20, 30 }; // Length 3 is not a power of 2 + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores)); // Scores should remain unchanged (original length 8) - assertEquals(8, miniMax.getScores().length); + Assertions.assertEquals(8, miniMax.getScores().length); } @Test void testSetScoresWithZeroLength() { int[] emptyScores = {}; // Length 0 is not a power of 2 - assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyScores)); + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyScores)); // Scores should remain unchanged (original length 8) - assertEquals(8, miniMax.getScores().length); + Assertions.assertEquals(8, miniMax.getScores().length); } @Test @@ -105,142 +100,143 @@ void testSetScoresWithVariousInvalidLengths() { // Test multiple invalid lengths to ensure isPowerOfTwo function is fully // covered int[][] invalidScoreArrays = { - {1, 2, 3, 4, 5}, // Length 5 - {1, 2, 3, 4, 5, 6}, // Length 6 - {1, 2, 3, 4, 5, 6, 7}, // Length 7 - new int[9], // Length 9 - new int[10], // Length 10 - new int[15] // Length 15 + { 1, 2, 3, 4, 5 }, // Length 5 + { 1, 2, 3, 4, 5, 6 }, // Length 6 + { 1, 2, 3, 4, 5, 6, 7 }, // Length 7 + new int[9], // Length 9 + new int[10], // Length 10 + new int[15] // Length 15 }; for (int[] invalidScores : invalidScoreArrays) { - assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores), "Failed for array length: " + invalidScores.length); + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores), + "Failed for array length: " + invalidScores.length); } // Scores should remain unchanged (original length 8) - assertEquals(8, miniMax.getScores().length); + Assertions.assertEquals(8, miniMax.getScores().length); } @Test void testSetScoresWithSingleElement() { - int[] singleScore = {42}; + int[] singleScore = { 42 }; miniMax.setScores(singleScore); - assertArrayEquals(singleScore, miniMax.getScores()); - assertEquals(0, miniMax.getHeight()); // log2(1) = 0 + Assertions.assertArrayEquals(singleScore, miniMax.getScores()); + Assertions.assertEquals(0, miniMax.getHeight()); // log2(1) = 0 } @Test void testMiniMaxWithKnownScores() { // Test with a known game tree: [3, 12, 8, 2] - int[] testScores = {3, 12, 8, 2}; + int[] testScores = { 3, 12, 8, 2 }; miniMax.setScores(testScores); // Maximizer starts: should choose max(min(3,12), min(8,2)) = max(3, 2) = 3 int result = miniMax.miniMax(0, true, 0, false); - assertEquals(3, result); + Assertions.assertEquals(3, result); } @Test void testMiniMaxWithMinimizerFirst() { // Test with minimizer starting first - int[] testScores = {3, 12, 8, 2}; + int[] testScores = { 3, 12, 8, 2 }; miniMax.setScores(testScores); // Minimizer starts: should choose min(max(3,12), max(8,2)) = min(12, 8) = 8 int result = miniMax.miniMax(0, false, 0, false); - assertEquals(8, result); + Assertions.assertEquals(8, result); } @Test void testMiniMaxWithLargerTree() { // Test with 8 elements: [5, 6, 7, 4, 5, 3, 6, 2] - int[] testScores = {5, 6, 7, 4, 5, 3, 6, 2}; + int[] testScores = { 5, 6, 7, 4, 5, 3, 6, 2 }; miniMax.setScores(testScores); // Maximizer starts int result = miniMax.miniMax(0, true, 0, false); // Expected: max(min(max(5,6), max(7,4)), min(max(5,3), max(6,2))) // = max(min(6, 7), min(5, 6)) = max(6, 5) = 6 - assertEquals(6, result); + Assertions.assertEquals(6, result); } @Test void testMiniMaxVerboseOutput() { - int[] testScores = {3, 12, 8, 2}; + int[] testScores = { 3, 12, 8, 2 }; miniMax.setScores(testScores); miniMax.miniMax(0, true, 0, true); String output = outputStream.toString(); - assertTrue(output.contains("Maximizer")); - assertTrue(output.contains("Minimizer")); - assertTrue(output.contains("chooses")); + Assertions.assertTrue(output.contains("Maximizer")); + Assertions.assertTrue(output.contains("Minimizer")); + Assertions.assertTrue(output.contains("chooses")); } @Test void testGetRandomScoresLength() { int[] randomScores = MiniMaxAlgorithm.getRandomScores(4, 50); - assertEquals(16, randomScores.length); // 2^4 = 16 + Assertions.assertEquals(16, randomScores.length); // 2^4 = 16 // All scores should be between 1 and 50 for (int score : randomScores) { - assertTrue(score >= 1 && score <= 50); + Assertions.assertTrue(score >= 1 && score <= 50); } } @Test void testGetRandomScoresWithDifferentParameters() { int[] randomScores = MiniMaxAlgorithm.getRandomScores(2, 10); - assertEquals(4, randomScores.length); // 2^2 = 4 + Assertions.assertEquals(4, randomScores.length); // 2^2 = 4 // All scores should be between 1 and 10 for (int score : randomScores) { - assertTrue(score >= 1 && score <= 10); + Assertions.assertTrue(score >= 1 && score <= 10); } } @Test void testMainMethod() { // Test that main method runs without errors - assertDoesNotThrow(() -> MiniMaxAlgorithm.main(new String[] {})); + Assertions.assertDoesNotThrow(() -> MiniMaxAlgorithm.main(new String[] {})); String output = outputStream.toString(); - assertTrue(output.contains("The best score for")); - assertTrue(output.contains("Maximizer")); + Assertions.assertTrue(output.contains("The best score for")); + Assertions.assertTrue(output.contains("Maximizer")); } @Test void testHeightCalculation() { // Test height calculation for different array sizes - int[] scores2 = {1, 2}; + int[] scores2 = { 1, 2 }; miniMax.setScores(scores2); - assertEquals(1, miniMax.getHeight()); // log2(2) = 1 + Assertions.assertEquals(1, miniMax.getHeight()); // log2(2) = 1 int[] scores16 = new int[16]; miniMax.setScores(scores16); - assertEquals(4, miniMax.getHeight()); // log2(16) = 4 + Assertions.assertEquals(4, miniMax.getHeight()); // log2(16) = 4 } @Test void testEdgeCaseWithZeroScores() { - int[] zeroScores = {0, 0, 0, 0}; + int[] zeroScores = { 0, 0, 0, 0 }; miniMax.setScores(zeroScores); int result = miniMax.miniMax(0, true, 0, false); - assertEquals(0, result); + Assertions.assertEquals(0, result); } @Test void testEdgeCaseWithNegativeScores() { - int[] negativeScores = {-5, -2, -8, -1}; + int[] negativeScores = { -5, -2, -8, -1 }; miniMax.setScores(negativeScores); // Tree evaluation with maximizer first: // Level 1 (minimizer): min(-5,-2) = -5, min(-8,-1) = -8 // Level 0 (maximizer): max(-5, -8) = -5 int result = miniMax.miniMax(0, true, 0, false); - assertEquals(-5, result); + Assertions.assertEquals(-5, result); } @Test @@ -251,9 +247,9 @@ void testSetScoresWithNegativeLength() { // Test with array length 0 (edge case for n > 0 condition) int[] emptyArray = new int[0]; - assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyArray)); + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyArray)); - assertEquals(8, miniMax.getScores().length); // Should remain unchanged + Assertions.assertEquals(8, miniMax.getScores().length); // Should remain unchanged } @Test @@ -265,34 +261,36 @@ void testSetScoresWithLargePowerOfTwo() { } miniMax.setScores(largeValidScores); - assertArrayEquals(largeValidScores, miniMax.getScores()); - assertEquals(5, miniMax.getHeight()); // log2(32) = 5 + Assertions.assertArrayEquals(largeValidScores, miniMax.getScores()); + Assertions.assertEquals(5, miniMax.getHeight()); // log2(32) = 5 } @Test void testSetScoresValidEdgeCases() { // Test valid powers of 2 to ensure isPowerOfTwo returns true correctly int[][] validPowersOf2 = { - new int[1], // 1 = 2^0 - new int[2], // 2 = 2^1 - new int[4], // 4 = 2^2 - new int[8], // 8 = 2^3 - new int[16], // 16 = 2^4 - new int[64] // 64 = 2^6 + new int[1], // 1 = 2^0 + new int[2], // 2 = 2^1 + new int[4], // 4 = 2^2 + new int[8], // 8 = 2^3 + new int[16], // 16 = 2^4 + new int[64] // 64 = 2^6 }; - int[] expectedHeights = {0, 1, 2, 3, 4, 6}; + int[] expectedHeights = { 0, 1, 2, 3, 4, 6 }; for (int i = 0; i < validPowersOf2.length; i++) { miniMax.setScores(validPowersOf2[i]); - assertEquals(validPowersOf2[i].length, miniMax.getScores().length, "Failed for array length: " + validPowersOf2[i].length); - assertEquals(expectedHeights[i], miniMax.getHeight(), "Height calculation failed for array length: " + validPowersOf2[i].length); + Assertions.assertEquals(validPowersOf2[i].length, miniMax.getScores().length, + "Failed for array length: " + validPowersOf2[i].length); + Assertions.assertEquals(expectedHeights[i], miniMax.getHeight(), + "Height calculation failed for array length: " + validPowersOf2[i].length); } } @Test void testGetScoresReturnsDefensiveCopy() { - int[] originalScores = {10, 20, 30, 40}; + int[] originalScores = { 10, 20, 30, 40 }; miniMax.setScores(originalScores); // Get the scores and modify them @@ -300,50 +298,50 @@ void testGetScoresReturnsDefensiveCopy() { retrievedScores[0] = 999; // Internal state should remain unchanged - assertEquals(10, miniMax.getScores()[0]); + Assertions.assertEquals(10, miniMax.getScores()[0]); } @Test void testSetScoresCreatesDefensiveCopy() { - int[] originalScores = {10, 20, 30, 40}; + int[] originalScores = { 10, 20, 30, 40 }; miniMax.setScores(originalScores); // Modify the original array after setting originalScores[0] = 999; // Internal state should remain unchanged - assertEquals(10, miniMax.getScores()[0]); + Assertions.assertEquals(10, miniMax.getScores()[0]); } @Test void testMiniMaxWithAllSameScores() { - int[] sameScores = {5, 5, 5, 5}; + int[] sameScores = { 5, 5, 5, 5 }; miniMax.setScores(sameScores); // When all scores are the same, result should be that score int result = miniMax.miniMax(0, true, 0, false); - assertEquals(5, result); + Assertions.assertEquals(5, result); } @Test void testMiniMaxAtDifferentDepths() { - int[] testScores = {3, 12, 8, 2, 14, 5, 2, 9}; + int[] testScores = { 3, 12, 8, 2, 14, 5, 2, 9 }; miniMax.setScores(testScores); // Test maximizer first int result = miniMax.miniMax(0, true, 0, false); // Expected: max(min(max(3,12), max(8,2)), min(max(14,5), max(2,9))) // = max(min(12, 8), min(14, 9)) = max(8, 9) = 9 - assertEquals(9, result); + Assertions.assertEquals(9, result); } @Test void testMiniMaxWithMinIntAndMaxInt() { - int[] extremeScores = {Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1}; + int[] extremeScores = { Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1 }; miniMax.setScores(extremeScores); int result = miniMax.miniMax(0, true, 0, false); // Expected: max(min(MIN, MAX), min(0, 1)) = max(MIN, 0) = 0 - assertEquals(0, result); + Assertions.assertEquals(0, result); } } From f55e22188955a0c788701f222f3a2358ee96e8d4 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 5 Oct 2025 15:29:31 +0530 Subject: [PATCH 3/5] Fix --- .../others/MiniMaxAlgorithmTest.java | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java index 54e50d892c41..4e81c8b7e34f 100644 --- a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java @@ -42,7 +42,7 @@ void testConstructorCreatesValidScores() { @Test void testConstructorWithValidScores() { - int[] validScores = { 10, 20, 30, 40 }; + int[] validScores = {10, 20, 30, 40}; MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(validScores); Assertions.assertArrayEquals(validScores, customMiniMax.getScores()); @@ -51,14 +51,14 @@ void testConstructorWithValidScores() { @Test void testConstructorWithInvalidScoresThrowsException() { - int[] invalidScores = { 10, 20, 30 }; // Length 3 is not a power of 2 + int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 Assertions.assertThrows(IllegalArgumentException.class, () -> new MiniMaxAlgorithm(invalidScores)); } @Test void testConstructorDoesNotModifyOriginalArray() { - int[] originalScores = { 10, 20, 30, 40 }; - int[] copyOfOriginal = { 10, 20, 30, 40 }; + int[] originalScores = {10, 20, 30, 40}; + int[] copyOfOriginal = {10, 20, 30, 40}; MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(originalScores); // Modify the original array @@ -70,7 +70,7 @@ void testConstructorDoesNotModifyOriginalArray() { @Test void testSetScoresWithValidPowerOfTwo() { - int[] validScores = { 10, 20, 30, 40 }; + int[] validScores = {10, 20, 30, 40}; miniMax.setScores(validScores); Assertions.assertArrayEquals(validScores, miniMax.getScores()); @@ -79,7 +79,7 @@ void testSetScoresWithValidPowerOfTwo() { @Test void testSetScoresWithInvalidLength() { - int[] invalidScores = { 10, 20, 30 }; // Length 3 is not a power of 2 + int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores)); // Scores should remain unchanged (original length 8) @@ -100,17 +100,16 @@ void testSetScoresWithVariousInvalidLengths() { // Test multiple invalid lengths to ensure isPowerOfTwo function is fully // covered int[][] invalidScoreArrays = { - { 1, 2, 3, 4, 5 }, // Length 5 - { 1, 2, 3, 4, 5, 6 }, // Length 6 - { 1, 2, 3, 4, 5, 6, 7 }, // Length 7 - new int[9], // Length 9 - new int[10], // Length 10 - new int[15] // Length 15 + {1, 2, 3, 4, 5}, // Length 5 + {1, 2, 3, 4, 5, 6}, // Length 6 + {1, 2, 3, 4, 5, 6, 7}, // Length 7 + new int[9], // Length 9 + new int[10], // Length 10 + new int[15] // Length 15 }; for (int[] invalidScores : invalidScoreArrays) { - Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores), - "Failed for array length: " + invalidScores.length); + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores), "Failed for array length: " + invalidScores.length); } // Scores should remain unchanged (original length 8) @@ -119,7 +118,7 @@ void testSetScoresWithVariousInvalidLengths() { @Test void testSetScoresWithSingleElement() { - int[] singleScore = { 42 }; + int[] singleScore = {42}; miniMax.setScores(singleScore); Assertions.assertArrayEquals(singleScore, miniMax.getScores()); @@ -129,7 +128,7 @@ void testSetScoresWithSingleElement() { @Test void testMiniMaxWithKnownScores() { // Test with a known game tree: [3, 12, 8, 2] - int[] testScores = { 3, 12, 8, 2 }; + int[] testScores = {3, 12, 8, 2}; miniMax.setScores(testScores); // Maximizer starts: should choose max(min(3,12), min(8,2)) = max(3, 2) = 3 @@ -140,7 +139,7 @@ void testMiniMaxWithKnownScores() { @Test void testMiniMaxWithMinimizerFirst() { // Test with minimizer starting first - int[] testScores = { 3, 12, 8, 2 }; + int[] testScores = {3, 12, 8, 2}; miniMax.setScores(testScores); // Minimizer starts: should choose min(max(3,12), max(8,2)) = min(12, 8) = 8 @@ -151,7 +150,7 @@ void testMiniMaxWithMinimizerFirst() { @Test void testMiniMaxWithLargerTree() { // Test with 8 elements: [5, 6, 7, 4, 5, 3, 6, 2] - int[] testScores = { 5, 6, 7, 4, 5, 3, 6, 2 }; + int[] testScores = {5, 6, 7, 4, 5, 3, 6, 2}; miniMax.setScores(testScores); // Maximizer starts @@ -163,7 +162,7 @@ void testMiniMaxWithLargerTree() { @Test void testMiniMaxVerboseOutput() { - int[] testScores = { 3, 12, 8, 2 }; + int[] testScores = {3, 12, 8, 2}; miniMax.setScores(testScores); miniMax.miniMax(0, true, 0, true); @@ -209,7 +208,7 @@ void testMainMethod() { @Test void testHeightCalculation() { // Test height calculation for different array sizes - int[] scores2 = { 1, 2 }; + int[] scores2 = {1, 2}; miniMax.setScores(scores2); Assertions.assertEquals(1, miniMax.getHeight()); // log2(2) = 1 @@ -220,7 +219,7 @@ void testHeightCalculation() { @Test void testEdgeCaseWithZeroScores() { - int[] zeroScores = { 0, 0, 0, 0 }; + int[] zeroScores = {0, 0, 0, 0}; miniMax.setScores(zeroScores); int result = miniMax.miniMax(0, true, 0, false); @@ -229,7 +228,7 @@ void testEdgeCaseWithZeroScores() { @Test void testEdgeCaseWithNegativeScores() { - int[] negativeScores = { -5, -2, -8, -1 }; + int[] negativeScores = {-5, -2, -8, -1}; miniMax.setScores(negativeScores); // Tree evaluation with maximizer first: @@ -269,28 +268,26 @@ void testSetScoresWithLargePowerOfTwo() { void testSetScoresValidEdgeCases() { // Test valid powers of 2 to ensure isPowerOfTwo returns true correctly int[][] validPowersOf2 = { - new int[1], // 1 = 2^0 - new int[2], // 2 = 2^1 - new int[4], // 4 = 2^2 - new int[8], // 8 = 2^3 - new int[16], // 16 = 2^4 - new int[64] // 64 = 2^6 + new int[1], // 1 = 2^0 + new int[2], // 2 = 2^1 + new int[4], // 4 = 2^2 + new int[8], // 8 = 2^3 + new int[16], // 16 = 2^4 + new int[64] // 64 = 2^6 }; - int[] expectedHeights = { 0, 1, 2, 3, 4, 6 }; + int[] expectedHeights = {0, 1, 2, 3, 4, 6}; for (int i = 0; i < validPowersOf2.length; i++) { miniMax.setScores(validPowersOf2[i]); - Assertions.assertEquals(validPowersOf2[i].length, miniMax.getScores().length, - "Failed for array length: " + validPowersOf2[i].length); - Assertions.assertEquals(expectedHeights[i], miniMax.getHeight(), - "Height calculation failed for array length: " + validPowersOf2[i].length); + Assertions.assertEquals(validPowersOf2[i].length, miniMax.getScores().length, "Failed for array length: " + validPowersOf2[i].length); + Assertions.assertEquals(expectedHeights[i], miniMax.getHeight(), "Height calculation failed for array length: " + validPowersOf2[i].length); } } @Test void testGetScoresReturnsDefensiveCopy() { - int[] originalScores = { 10, 20, 30, 40 }; + int[] originalScores = {10, 20, 30, 40}; miniMax.setScores(originalScores); // Get the scores and modify them @@ -303,7 +300,7 @@ void testGetScoresReturnsDefensiveCopy() { @Test void testSetScoresCreatesDefensiveCopy() { - int[] originalScores = { 10, 20, 30, 40 }; + int[] originalScores = {10, 20, 30, 40}; miniMax.setScores(originalScores); // Modify the original array after setting @@ -315,7 +312,7 @@ void testSetScoresCreatesDefensiveCopy() { @Test void testMiniMaxWithAllSameScores() { - int[] sameScores = { 5, 5, 5, 5 }; + int[] sameScores = {5, 5, 5, 5}; miniMax.setScores(sameScores); // When all scores are the same, result should be that score @@ -325,7 +322,7 @@ void testMiniMaxWithAllSameScores() { @Test void testMiniMaxAtDifferentDepths() { - int[] testScores = { 3, 12, 8, 2, 14, 5, 2, 9 }; + int[] testScores = {3, 12, 8, 2, 14, 5, 2, 9}; miniMax.setScores(testScores); // Test maximizer first @@ -337,7 +334,7 @@ void testMiniMaxAtDifferentDepths() { @Test void testMiniMaxWithMinIntAndMaxInt() { - int[] extremeScores = { Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1 }; + int[] extremeScores = {Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1}; miniMax.setScores(extremeScores); int result = miniMax.miniMax(0, true, 0, false); From 9e8afb4f2c32b4d1c4acadaa0215c2eee23a040f Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 5 Oct 2025 15:44:11 +0530 Subject: [PATCH 4/5] refactor: Enhance docs, code, add tests in `HappyNumbersSeq` --- .../thealgorithms/others/HappyNumbersSeq.java | 85 ++++++++- .../others/HappyNumbersSeqTest.java | 179 ++++++++++++++++++ 2 files changed, 257 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java diff --git a/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java b/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java index 0ae1e451bc6a..589b5941c370 100644 --- a/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java +++ b/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java @@ -5,12 +5,52 @@ import java.util.Scanner; import java.util.Set; +/** + * A utility class for working with Happy Numbers. + * + *

+ * A Happy Number is defined by the following process: + * Starting with any positive integer, replace the number by the sum of the + * squares of its digits. + * Repeat the process until the number equals 1 (where it will stay), or it + * loops endlessly in a + * cycle which does not include 1. + * Those numbers for which this process ends in 1 are happy numbers, while those + * that do not end + * in 1 are unhappy (or sad) numbers. + * + *

+ * For example: + *

+ * + * @see Happy Number - + * Wikipedia + * @see Happy Number - + * Wolfram MathWorld + */ public final class HappyNumbersSeq { private HappyNumbersSeq() { } + /** + * Known cycle numbers that indicate a sad number. + * If the sequence reaches any of these numbers, it will cycle indefinitely + * without reaching 1. + */ private static final Set CYCLE_NUMS = new HashSet<>(Arrays.asList(4, 16, 20, 37, 58, 145)); + /** + * Main method to demonstrate happy number detection. + * Reads a number from user input and displays the sequence until it reaches 1 + * (happy) + * or enters a cycle (sad). + * + * @param args command-line arguments (not used) + */ public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter number: "); @@ -24,16 +64,47 @@ public static void main(String[] args) { in.close(); } - private static int sumSquares(int n) { - int s = 0; - for (; n > 0; n /= 10) { - int r = n % 10; - s += r * r; + /** + * Determines if a number is a happy number. + * + * @param n the number to check (must be positive) + * @return {@code true} if the number is happy, {@code false} otherwise + * @throws IllegalArgumentException if n is not positive + */ + public static boolean isHappy(int n) { + if (n <= 0) { + throw new IllegalArgumentException("Number must be positive"); } - return s; + while (n != 1 && !isSad(n)) { + n = sumSquares(n); + } + return n == 1; + } + + /** + * Computes the sum of the squares of the digits of a number. + * + * @param n the number whose digits will be squared and summed + * @return the sum of the squares of the digits + */ + static int sumSquares(int n) { + int sum = 0; + while (n > 0) { + int digit = n % 10; + sum += digit * digit; + n /= 10; + } + return sum; } - private static boolean isSad(int n) { + /** + * Checks if a number is part of the known cycle that indicates a sad number. + * + * @param n the number to check + * @return {@code true} if the number is in the sad cycle, {@code false} + * otherwise + */ + static boolean isSad(int n) { return CYCLE_NUMS.contains(n); } } diff --git a/src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java b/src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java new file mode 100644 index 000000000000..ea45dd5bdfde --- /dev/null +++ b/src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java @@ -0,0 +1,179 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test class for {@link HappyNumbersSeq}. + * + * @author Hardvan (https://github.com/Hardvan) + */ +class HappyNumbersSeqTest { + + @Test + void testIsHappyWithHappyNumbers() { + // Test known happy numbers + assertTrue(HappyNumbersSeq.isHappy(1)); + assertTrue(HappyNumbersSeq.isHappy(7)); + assertTrue(HappyNumbersSeq.isHappy(10)); + assertTrue(HappyNumbersSeq.isHappy(13)); + assertTrue(HappyNumbersSeq.isHappy(19)); + assertTrue(HappyNumbersSeq.isHappy(23)); + assertTrue(HappyNumbersSeq.isHappy(28)); + assertTrue(HappyNumbersSeq.isHappy(31)); + assertTrue(HappyNumbersSeq.isHappy(32)); + assertTrue(HappyNumbersSeq.isHappy(44)); + assertTrue(HappyNumbersSeq.isHappy(49)); + assertTrue(HappyNumbersSeq.isHappy(68)); + assertTrue(HappyNumbersSeq.isHappy(70)); + assertTrue(HappyNumbersSeq.isHappy(79)); + assertTrue(HappyNumbersSeq.isHappy(82)); + assertTrue(HappyNumbersSeq.isHappy(86)); + assertTrue(HappyNumbersSeq.isHappy(91)); + assertTrue(HappyNumbersSeq.isHappy(94)); + assertTrue(HappyNumbersSeq.isHappy(97)); + assertTrue(HappyNumbersSeq.isHappy(100)); + } + + @Test + void testIsHappyWithSadNumbers() { + // Test known sad numbers + assertFalse(HappyNumbersSeq.isHappy(2)); + assertFalse(HappyNumbersSeq.isHappy(3)); + assertFalse(HappyNumbersSeq.isHappy(4)); + assertFalse(HappyNumbersSeq.isHappy(5)); + assertFalse(HappyNumbersSeq.isHappy(6)); + assertFalse(HappyNumbersSeq.isHappy(8)); + assertFalse(HappyNumbersSeq.isHappy(9)); + assertFalse(HappyNumbersSeq.isHappy(11)); + assertFalse(HappyNumbersSeq.isHappy(12)); + assertFalse(HappyNumbersSeq.isHappy(14)); + assertFalse(HappyNumbersSeq.isHappy(15)); + assertFalse(HappyNumbersSeq.isHappy(16)); + assertFalse(HappyNumbersSeq.isHappy(17)); + assertFalse(HappyNumbersSeq.isHappy(18)); + assertFalse(HappyNumbersSeq.isHappy(20)); + } + + @Test + void testIsHappyWithLargeNumbers() { + // Test larger happy numbers + assertTrue(HappyNumbersSeq.isHappy(1000)); + assertFalse(HappyNumbersSeq.isHappy(999)); + assertFalse(HappyNumbersSeq.isHappy(1001)); + } + + @Test + void testIsHappyWithInvalidInput() { + // Test with zero + assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(0)); + + // Test with negative numbers + assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(-1)); + assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(-10)); + assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(-100)); + } + + @Test + void testSumSquaresSingleDigit() { + assertEquals(0, HappyNumbersSeq.sumSquares(0)); + assertEquals(1, HappyNumbersSeq.sumSquares(1)); + assertEquals(4, HappyNumbersSeq.sumSquares(2)); + assertEquals(9, HappyNumbersSeq.sumSquares(3)); + assertEquals(16, HappyNumbersSeq.sumSquares(4)); + assertEquals(25, HappyNumbersSeq.sumSquares(5)); + assertEquals(36, HappyNumbersSeq.sumSquares(6)); + assertEquals(49, HappyNumbersSeq.sumSquares(7)); + assertEquals(64, HappyNumbersSeq.sumSquares(8)); + assertEquals(81, HappyNumbersSeq.sumSquares(9)); + } + + @Test + void testSumSquaresMultipleDigits() { + // 10: 1^2 + 0^2 = 1 + assertEquals(1, HappyNumbersSeq.sumSquares(10)); + + // 23: 2^2 + 3^2 = 4 + 9 = 13 + assertEquals(13, HappyNumbersSeq.sumSquares(23)); + + // 82: 8^2 + 2^2 = 64 + 4 = 68 + assertEquals(68, HappyNumbersSeq.sumSquares(82)); + + // 130: 1^2 + 3^2 + 0^2 = 1 + 9 + 0 = 10 + assertEquals(10, HappyNumbersSeq.sumSquares(130)); + + // 999: 9^2 + 9^2 + 9^2 = 81 + 81 + 81 = 243 + assertEquals(243, HappyNumbersSeq.sumSquares(999)); + } + + @Test + void testSumSquaresLargeNumbers() { + // 1234: 1^2 + 2^2 + 3^2 + 4^2 = 1 + 4 + 9 + 16 = 30 + assertEquals(30, HappyNumbersSeq.sumSquares(1234)); + + // 9876: 9^2 + 8^2 + 7^2 + 6^2 = 81 + 64 + 49 + 36 = 230 + assertEquals(230, HappyNumbersSeq.sumSquares(9876)); + } + + @Test + void testIsSadWithCycleNumbers() { + // Test all known cycle numbers + assertTrue(HappyNumbersSeq.isSad(4)); + assertTrue(HappyNumbersSeq.isSad(16)); + assertTrue(HappyNumbersSeq.isSad(20)); + assertTrue(HappyNumbersSeq.isSad(37)); + assertTrue(HappyNumbersSeq.isSad(58)); + assertTrue(HappyNumbersSeq.isSad(145)); + } + + @Test + void testIsSadWithNonCycleNumbers() { + // Test numbers that are not in the cycle + assertFalse(HappyNumbersSeq.isSad(1)); + assertFalse(HappyNumbersSeq.isSad(7)); + assertFalse(HappyNumbersSeq.isSad(10)); + assertFalse(HappyNumbersSeq.isSad(13)); + assertFalse(HappyNumbersSeq.isSad(19)); + assertFalse(HappyNumbersSeq.isSad(23)); + } + + @Test + void testHappyNumberSequenceFor7() { + // Test the sequence for happy number 7: 7 → 49 → 97 → 130 → 10 → 1 + int n = 7; + assertEquals(49, HappyNumbersSeq.sumSquares(n)); + n = 49; + assertEquals(97, HappyNumbersSeq.sumSquares(n)); + n = 97; + assertEquals(130, HappyNumbersSeq.sumSquares(n)); + n = 130; + assertEquals(10, HappyNumbersSeq.sumSquares(n)); + n = 10; + assertEquals(1, HappyNumbersSeq.sumSquares(n)); + } + + @Test + void testHappyNumberSequenceFor19() { + // Test the sequence for happy number 19: 19 → 82 → 68 → 100 → 1 + int n = 19; + assertEquals(82, HappyNumbersSeq.sumSquares(n)); + n = 82; + assertEquals(68, HappyNumbersSeq.sumSquares(n)); + n = 68; + assertEquals(100, HappyNumbersSeq.sumSquares(n)); + n = 100; + assertEquals(1, HappyNumbersSeq.sumSquares(n)); + } + + @Test + void testSadNumberEntersCycle() { + // Test that sad number 2 eventually reaches a cycle number + int n = 2; + assertEquals(4, HappyNumbersSeq.sumSquares(n)); // 2 → 4 (cycle number) + assertTrue(HappyNumbersSeq.isSad(4)); + } +} From 93a389807bfe9578f89b53f0a0c950f698f846fc Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 5 Oct 2025 15:46:48 +0530 Subject: [PATCH 5/5] Revert "refactor: Enhance docs, code, add tests in `HappyNumbersSeq`" This reverts commit 9e8afb4f2c32b4d1c4acadaa0215c2eee23a040f. --- .../thealgorithms/others/HappyNumbersSeq.java | 85 +-------- .../others/HappyNumbersSeqTest.java | 179 ------------------ 2 files changed, 7 insertions(+), 257 deletions(-) delete mode 100644 src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java diff --git a/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java b/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java index 589b5941c370..0ae1e451bc6a 100644 --- a/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java +++ b/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java @@ -5,52 +5,12 @@ import java.util.Scanner; import java.util.Set; -/** - * A utility class for working with Happy Numbers. - * - *

- * A Happy Number is defined by the following process: - * Starting with any positive integer, replace the number by the sum of the - * squares of its digits. - * Repeat the process until the number equals 1 (where it will stay), or it - * loops endlessly in a - * cycle which does not include 1. - * Those numbers for which this process ends in 1 are happy numbers, while those - * that do not end - * in 1 are unhappy (or sad) numbers. - * - *

- * For example: - *

    - *
  • 7 is a happy number: 7 → 49 → 97 → 130 → 10 → 1
  • - *
  • 2 is not a happy number (sad number): 2 → 4 → 16 → 37 → 58 → 89 → 145 → - * 42 → 20 → 4 (cycle)
  • - *
- * - * @see Happy Number - - * Wikipedia - * @see Happy Number - - * Wolfram MathWorld - */ public final class HappyNumbersSeq { private HappyNumbersSeq() { } - /** - * Known cycle numbers that indicate a sad number. - * If the sequence reaches any of these numbers, it will cycle indefinitely - * without reaching 1. - */ private static final Set CYCLE_NUMS = new HashSet<>(Arrays.asList(4, 16, 20, 37, 58, 145)); - /** - * Main method to demonstrate happy number detection. - * Reads a number from user input and displays the sequence until it reaches 1 - * (happy) - * or enters a cycle (sad). - * - * @param args command-line arguments (not used) - */ public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter number: "); @@ -64,47 +24,16 @@ public static void main(String[] args) { in.close(); } - /** - * Determines if a number is a happy number. - * - * @param n the number to check (must be positive) - * @return {@code true} if the number is happy, {@code false} otherwise - * @throws IllegalArgumentException if n is not positive - */ - public static boolean isHappy(int n) { - if (n <= 0) { - throw new IllegalArgumentException("Number must be positive"); + private static int sumSquares(int n) { + int s = 0; + for (; n > 0; n /= 10) { + int r = n % 10; + s += r * r; } - while (n != 1 && !isSad(n)) { - n = sumSquares(n); - } - return n == 1; - } - - /** - * Computes the sum of the squares of the digits of a number. - * - * @param n the number whose digits will be squared and summed - * @return the sum of the squares of the digits - */ - static int sumSquares(int n) { - int sum = 0; - while (n > 0) { - int digit = n % 10; - sum += digit * digit; - n /= 10; - } - return sum; + return s; } - /** - * Checks if a number is part of the known cycle that indicates a sad number. - * - * @param n the number to check - * @return {@code true} if the number is in the sad cycle, {@code false} - * otherwise - */ - static boolean isSad(int n) { + private static boolean isSad(int n) { return CYCLE_NUMS.contains(n); } } diff --git a/src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java b/src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java deleted file mode 100644 index ea45dd5bdfde..000000000000 --- a/src/test/java/com/thealgorithms/others/HappyNumbersSeqTest.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.thealgorithms.others; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -/** - * Test class for {@link HappyNumbersSeq}. - * - * @author Hardvan (https://github.com/Hardvan) - */ -class HappyNumbersSeqTest { - - @Test - void testIsHappyWithHappyNumbers() { - // Test known happy numbers - assertTrue(HappyNumbersSeq.isHappy(1)); - assertTrue(HappyNumbersSeq.isHappy(7)); - assertTrue(HappyNumbersSeq.isHappy(10)); - assertTrue(HappyNumbersSeq.isHappy(13)); - assertTrue(HappyNumbersSeq.isHappy(19)); - assertTrue(HappyNumbersSeq.isHappy(23)); - assertTrue(HappyNumbersSeq.isHappy(28)); - assertTrue(HappyNumbersSeq.isHappy(31)); - assertTrue(HappyNumbersSeq.isHappy(32)); - assertTrue(HappyNumbersSeq.isHappy(44)); - assertTrue(HappyNumbersSeq.isHappy(49)); - assertTrue(HappyNumbersSeq.isHappy(68)); - assertTrue(HappyNumbersSeq.isHappy(70)); - assertTrue(HappyNumbersSeq.isHappy(79)); - assertTrue(HappyNumbersSeq.isHappy(82)); - assertTrue(HappyNumbersSeq.isHappy(86)); - assertTrue(HappyNumbersSeq.isHappy(91)); - assertTrue(HappyNumbersSeq.isHappy(94)); - assertTrue(HappyNumbersSeq.isHappy(97)); - assertTrue(HappyNumbersSeq.isHappy(100)); - } - - @Test - void testIsHappyWithSadNumbers() { - // Test known sad numbers - assertFalse(HappyNumbersSeq.isHappy(2)); - assertFalse(HappyNumbersSeq.isHappy(3)); - assertFalse(HappyNumbersSeq.isHappy(4)); - assertFalse(HappyNumbersSeq.isHappy(5)); - assertFalse(HappyNumbersSeq.isHappy(6)); - assertFalse(HappyNumbersSeq.isHappy(8)); - assertFalse(HappyNumbersSeq.isHappy(9)); - assertFalse(HappyNumbersSeq.isHappy(11)); - assertFalse(HappyNumbersSeq.isHappy(12)); - assertFalse(HappyNumbersSeq.isHappy(14)); - assertFalse(HappyNumbersSeq.isHappy(15)); - assertFalse(HappyNumbersSeq.isHappy(16)); - assertFalse(HappyNumbersSeq.isHappy(17)); - assertFalse(HappyNumbersSeq.isHappy(18)); - assertFalse(HappyNumbersSeq.isHappy(20)); - } - - @Test - void testIsHappyWithLargeNumbers() { - // Test larger happy numbers - assertTrue(HappyNumbersSeq.isHappy(1000)); - assertFalse(HappyNumbersSeq.isHappy(999)); - assertFalse(HappyNumbersSeq.isHappy(1001)); - } - - @Test - void testIsHappyWithInvalidInput() { - // Test with zero - assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(0)); - - // Test with negative numbers - assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(-1)); - assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(-10)); - assertThrows(IllegalArgumentException.class, () -> HappyNumbersSeq.isHappy(-100)); - } - - @Test - void testSumSquaresSingleDigit() { - assertEquals(0, HappyNumbersSeq.sumSquares(0)); - assertEquals(1, HappyNumbersSeq.sumSquares(1)); - assertEquals(4, HappyNumbersSeq.sumSquares(2)); - assertEquals(9, HappyNumbersSeq.sumSquares(3)); - assertEquals(16, HappyNumbersSeq.sumSquares(4)); - assertEquals(25, HappyNumbersSeq.sumSquares(5)); - assertEquals(36, HappyNumbersSeq.sumSquares(6)); - assertEquals(49, HappyNumbersSeq.sumSquares(7)); - assertEquals(64, HappyNumbersSeq.sumSquares(8)); - assertEquals(81, HappyNumbersSeq.sumSquares(9)); - } - - @Test - void testSumSquaresMultipleDigits() { - // 10: 1^2 + 0^2 = 1 - assertEquals(1, HappyNumbersSeq.sumSquares(10)); - - // 23: 2^2 + 3^2 = 4 + 9 = 13 - assertEquals(13, HappyNumbersSeq.sumSquares(23)); - - // 82: 8^2 + 2^2 = 64 + 4 = 68 - assertEquals(68, HappyNumbersSeq.sumSquares(82)); - - // 130: 1^2 + 3^2 + 0^2 = 1 + 9 + 0 = 10 - assertEquals(10, HappyNumbersSeq.sumSquares(130)); - - // 999: 9^2 + 9^2 + 9^2 = 81 + 81 + 81 = 243 - assertEquals(243, HappyNumbersSeq.sumSquares(999)); - } - - @Test - void testSumSquaresLargeNumbers() { - // 1234: 1^2 + 2^2 + 3^2 + 4^2 = 1 + 4 + 9 + 16 = 30 - assertEquals(30, HappyNumbersSeq.sumSquares(1234)); - - // 9876: 9^2 + 8^2 + 7^2 + 6^2 = 81 + 64 + 49 + 36 = 230 - assertEquals(230, HappyNumbersSeq.sumSquares(9876)); - } - - @Test - void testIsSadWithCycleNumbers() { - // Test all known cycle numbers - assertTrue(HappyNumbersSeq.isSad(4)); - assertTrue(HappyNumbersSeq.isSad(16)); - assertTrue(HappyNumbersSeq.isSad(20)); - assertTrue(HappyNumbersSeq.isSad(37)); - assertTrue(HappyNumbersSeq.isSad(58)); - assertTrue(HappyNumbersSeq.isSad(145)); - } - - @Test - void testIsSadWithNonCycleNumbers() { - // Test numbers that are not in the cycle - assertFalse(HappyNumbersSeq.isSad(1)); - assertFalse(HappyNumbersSeq.isSad(7)); - assertFalse(HappyNumbersSeq.isSad(10)); - assertFalse(HappyNumbersSeq.isSad(13)); - assertFalse(HappyNumbersSeq.isSad(19)); - assertFalse(HappyNumbersSeq.isSad(23)); - } - - @Test - void testHappyNumberSequenceFor7() { - // Test the sequence for happy number 7: 7 → 49 → 97 → 130 → 10 → 1 - int n = 7; - assertEquals(49, HappyNumbersSeq.sumSquares(n)); - n = 49; - assertEquals(97, HappyNumbersSeq.sumSquares(n)); - n = 97; - assertEquals(130, HappyNumbersSeq.sumSquares(n)); - n = 130; - assertEquals(10, HappyNumbersSeq.sumSquares(n)); - n = 10; - assertEquals(1, HappyNumbersSeq.sumSquares(n)); - } - - @Test - void testHappyNumberSequenceFor19() { - // Test the sequence for happy number 19: 19 → 82 → 68 → 100 → 1 - int n = 19; - assertEquals(82, HappyNumbersSeq.sumSquares(n)); - n = 82; - assertEquals(68, HappyNumbersSeq.sumSquares(n)); - n = 68; - assertEquals(100, HappyNumbersSeq.sumSquares(n)); - n = 100; - assertEquals(1, HappyNumbersSeq.sumSquares(n)); - } - - @Test - void testSadNumberEntersCycle() { - // Test that sad number 2 eventually reaches a cycle number - int n = 2; - assertEquals(4, HappyNumbersSeq.sumSquares(n)); // 2 → 4 (cycle number) - assertTrue(HappyNumbersSeq.isSad(4)); - } -}