Repository: TheAlgorithms/Java
Pull request #4207 (2023-05-29 18:55:06)

refactor: `LowestBasePalindrome`
The class [`LowestBasePalindrome`](https://github.com/TheAlgorithms/Java/blob/96c1a96647c947f8f0c531ade3bc18967359e0ea/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java#L17) has several problems:
- it is not tested,
- the method [`LowestBasePalindrome::lowestBasePalindrome`](https://github.com/TheAlgorithms/Java/blob/96c1a96647c947f8f0c531ade3bc18967359e0ea/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java#LL49C23-L49C43) falls into an _infinite loop_ for inputs `2` and `3`,
- does not work properly for [_large_ bases](https://github.com/TheAlgorithms/Java/blob/96c1a96647c947f8f0c531ade3bc18967359e0ea/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java#L72),
- has some [dead code](https://github.com/TheAlgorithms/Java/blob/96c1a96647c947f8f0c531ade3bc18967359e0ea/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java#L97).

This PR does complete refactoring of the mentioned class and solves above problems. The test data has been verified with [A016026](https://oeis.org/A016026).

<!-- For completed items, change [ ] to [x] -->

- [x] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Java/blob/master/CONTRIBUTING.md).
- [x] This pull request is all my own work -- I have not plagiarized it.
- [x] All filenames are in PascalCase.
- [x] All functions and variable names follow Java naming conventions.
- [x] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.



Patch:
diff --git a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java b/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
index 3d50b484..addf8255 100644
--- a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
+++ b/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java
@@ -1,153 +1,93 @@
 package com.thealgorithms.others;
 
-import java.util.InputMismatchException;
-import java.util.Scanner;
+import java.util.ArrayList;
 
 /**
- * Class for finding the lowest base in which a given integer is a palindrome.
- * Includes auxiliary methods for converting between bases and reversing
- * strings.
- *
- * <p>
- * NOTE: There is potential for error, see note at line 63.
- *
- * @author RollandMichael
- * @version 2017.09.28
+ * @brief Class for finding the lowest base in which a given integer is a palindrome.
+     cf. https://oeis.org/A016026
  */
-public class LowestBasePalindrome {
+final public class LowestBasePalindrome {
+    private LowestBasePalindrome() {
+    }
 
-    public static void main(String[] args) {
-        Scanner in = new Scanner(System.in);
-        int n = 0;
-        while (true) {
-            try {
-                System.out.print("Enter number: ");
-                n = in.nextInt();
-                break;
-            } catch (InputMismatchException e) {
-                System.out.println("Invalid input!");
-                in.next();
-            }
+    private static void checkBase(int base) {
+        if (base <= 1) {
+            throw new IllegalArgumentException("base must be greater than 1.");
+        }
+    }
+
+    private static void checkNumber(int number) {
+        if (number < 0) {
+            throw new IllegalArgumentException("number must be nonnegative.");
         }
-        System.out.println(
-            n + " is a palindrome in base " + lowestBasePalindrome(n)
-        );
-        System.out.println(
-            base2base(Integer.toString(n), 10, lowestBasePalindrome(n))
-        );
-        in.close();
     }
 
     /**
-     * Given a number in base 10, returns the lowest base in which the number is
-     * represented by a palindrome (read the same left-to-right and
-     * right-to-left).
-     *
-     * @param num A number in base 10.
-     * @return The lowest base in which num is a palindrome.
+     * @brief computes the representation of the input number in given base
+     * @param number the input number
+     * @param base the given base
+     * @exception IllegalArgumentException number is negative or base is less than 2
+     * @return the list containing the digits of the input number in the given base, the most significant digit is at the end of the array
      */
-    public static int lowestBasePalindrome(int num) {
-        int base, num2 = num;
-        int digit;
-        char digitC;
-        boolean foundBase = false;
-        String newNum = "";
-        String digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-
-        while (!foundBase) {
-            // Try from bases 2 to num-1
-            for (base = 2; base < num2; base++) {
-                newNum = "";
-                while (num > 0) {
-                    // Obtain the first digit of n in the current base,
-                    // which is equivalent to the integer remainder of (n/base).
-                    // The next digit is obtained by dividing n by the base and
-                    // continuing the process of getting the remainder. This is done
-                    // until n is <=0 and the number in the new base is obtained.
-                    digit = (num % base);
-                    num /= base;
-                    // If the digit isn't in the set of [0-9][A-Z] (beyond base 36), its character
-                    // form is just its value in ASCII.
+    public static ArrayList<Integer> computeDigitsInBase(int number, int base) {
+        checkNumber(number);
+        checkBase(base);
+        var result = new ArrayList<Integer>();
+        while (number > 0) {
+            result.add(number % base);
+            number /= base;
+        }
+        return result;
+    }
 
-                    // NOTE: This may cause problems, as the capital letters are ASCII values
-                    // 65-90. It may cause false positives when one digit is, for instance 10 and assigned
-                    // 'A' from the character array and the other is 65 and also assigned 'A'.
-                    // Regardless, the character is added to the representation of n
-                    // in the current base.
-                    if (digit >= digits.length()) {
-                        digitC = (char) (digit);
-                        newNum += digitC;
-                        continue;
-                    }
-                    newNum += digits.charAt(digit);
-                }
-                // Num is assigned back its original value for the next iteration.
-                num = num2;
-                // Auxiliary method reverses the number.
-                String reverse = reverse(newNum);
-                // If the number is read the same as its reverse, then it is a palindrome.
-                // The current base is returned.
-                if (reverse.equals(newNum)) {
-                    foundBase = true;
-                    return base;
-                }
+    /**
+     * @brief checks if the input array is a palindrome
+     * @brief list the input array
+     * @return true, if the input array is a palindrome, false otherwise
+     */
+    public static boolean isPalindromic(ArrayList<Integer> list) {
+        for (int pos = 0; pos < list.size()/2; ++pos) {
+            if(list.get(pos) != list.get(list.size()-1-pos)) {
+                return false;
             }
         }
-        // If all else fails, n is always a palindrome in base n-1. ("11")
-        return num - 1;
+        return true;
     }
 
-    private static String reverse(String str) {
-        String reverse = "";
-        for (int i = str.length() - 1; i >= 0; i--) {
-            reverse += str.charAt(i);
+    /**
+     * @brief checks if representation of the input number in given base is a palindrome
+     * @param number the input number
+     * @param base the given base
+     * @exception IllegalArgumentException number is negative or base is less than 2
+     * @return true, if the input number represented in the given base is a palindrome, false otherwise
+     */
+    public static boolean isPalindromicInBase(int number, int base) {
+        checkNumber(number);
+        checkBase(base);
+
+        if (number <= 1) {
+            return true;
         }
-        return reverse;
-    }
 
-    private static String base2base(String n, int b1, int b2) {
-        // Declare variables: decimal value of n,
-        // character of base b1, character of base b2,
-        // and the string that will be returned.
-        int decimalValue = 0, charB2;
-        char charB1;
-        String output = "";
-        // Go through every character of n
-        for (int i = 0; i < n.length(); i++) {
-            // store the character in charB1
-            charB1 = n.charAt(i);
-            // if it is a non-number, convert it to a decimal value >9 and store it in charB2
-            if (charB1 >= 'A' && charB1 <= 'Z') {
-                charB2 = 10 + (charB1 - 'A');
-            } // Else, store the integer value in charB2
-            else {
-                charB2 = charB1 - '0';
-            }
-            // Convert the digit to decimal and add it to the
-            // decimalValue of n
-            decimalValue = decimalValue * b1 + charB2;
+        if (number % base == 0) {
+            // the last digit of number written in base is 0
+            return false;
         }
 
-        // Converting the decimal value to base b2:
-        // A number is converted from decimal to another base
-        // by continuously dividing by the base and recording
-        // the remainder until the quotient is zero. The number in the
-        // new base is the remainders, with the last remainder
-        // being the left-most digit.
-        // While the quotient is NOT zero:
-        while (decimalValue != 0) {
-            // If the remainder is a digit < 10, simply add it to
-            // the left side of the new number.
-            if (decimalValue % b2 < 10) {
-                output = decimalValue % b2 + output;
-            } // If the remainder is >= 10, add a character with the
-            // corresponding value to the new number. (A = 10, B = 11, C = 12, ...)
-            else {
-                output = (char) ((decimalValue % b2) + 55) + output;
-            }
-            // Divide by the new base again
-            decimalValue /= b2;
+        return isPalindromic(computeDigitsInBase(number, base));
+    }
+
+    /**
+     * @brief finds the smallest base for which the representation of the input number is a palindrome
+     * @param number the input number
+     * @exception IllegalArgumentException number is negative
+     * @return the smallest base for which the representation of the input number is a palindrome
+     */
+    public static int lowestBasePalindrome(int number) {
+        int base = 2;
+        while(!isPalindromicInBase(number, base)) {
+            ++base;
         }
-        return output;
+        return base;
     }
 }


Test patch:
diff --git a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java b/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java
new file mode 100644
index 00000000..3124d7b0
--- /dev/null
+++ b/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java
@@ -0,0 +1,87 @@
+package com.thealgorithms.others;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+public class LowestBasePalindromeTest {
+    @Test
+    public void testIsPalindromicPositive() {
+        assertTrue(LowestBasePalindrome.isPalindromic(new ArrayList<Integer>()));
+        assertTrue(LowestBasePalindrome.isPalindromic(new ArrayList<Integer>(Arrays.asList(1))));
+        assertTrue(LowestBasePalindrome.isPalindromic(new ArrayList<Integer>(Arrays.asList(1, 1))));
+        assertTrue(LowestBasePalindrome.isPalindromic(new ArrayList<Integer>(Arrays.asList(1, 2, 1))));
+        assertTrue(LowestBasePalindrome.isPalindromic(new ArrayList<Integer>(Arrays.asList(1, 2, 2, 1))));
+    }
+
+    @Test
+    public void testIsPalindromicNegative() {
+        assertFalse(LowestBasePalindrome.isPalindromic(new ArrayList<Integer>(Arrays.asList(1, 2))));
+        assertFalse(LowestBasePalindrome.isPalindromic(new ArrayList<Integer>(Arrays.asList(1, 2, 1, 1))));
+    }
+
+    @Test
+    public void testIsPalindromicInBasePositive() {
+        assertTrue(LowestBasePalindrome.isPalindromicInBase(101, 10));
+        assertTrue(LowestBasePalindrome.isPalindromicInBase(1, 190));
+        assertTrue(LowestBasePalindrome.isPalindromicInBase(0, 11));
+        assertTrue(LowestBasePalindrome.isPalindromicInBase(10101, 10));
+        assertTrue(LowestBasePalindrome.isPalindromicInBase(23, 22));
+    }
+
+    @Test
+    public void testIsPalindromicInBaseNegative() {
+        assertFalse(LowestBasePalindrome.isPalindromicInBase(1010, 10));
+        assertFalse(LowestBasePalindrome.isPalindromicInBase(123, 10));
+    }
+
+    @Test
+    public void testIsPalindromicInBaseThrowsExceptionForNegativeNumbers() {
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> LowestBasePalindrome.isPalindromicInBase(-1, 5)
+        );
+    }
+
+    @Test
+    public void testIsPalindromicInBaseThrowsExceptionForWrongBases() {
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> LowestBasePalindrome.isPalindromicInBase(10, 1)
+        );
+    }
+
+    @Test
+    public void testLowestBasePalindrome() {
+        HashMap<Integer, Integer> testCases = new HashMap<>();
+        testCases.put(0, 2);
+        testCases.put(1, 2);
+        testCases.put(2, 3);
+        testCases.put(3, 2);
+        testCases.put(10, 3);
+        testCases.put(11, 10);
+        testCases.put(15, 2);
+        testCases.put(39, 12);
+        testCases.put(44, 10);
+        testCases.put(58, 28);
+        testCases.put(69, 22);
+        testCases.put(79, 78);
+        testCases.put(87, 28);
+        testCases.put(90, 14);
+        testCases.put(5591, 37);
+        testCases.put(5895, 130);
+        testCases.put(9950, 198);
+        testCases.put(9974, 4986);
+
+        for (final var tc : testCases.entrySet()) {
+            assertEquals(LowestBasePalindrome.lowestBasePalindrome(tc.getKey()), tc.getValue());
+        }
+    }
+}