Repository: TheAlgorithms/Java
Pull request #5562 (2024-10-03 20:30:17)

Add Tests for HillCipher.java
<!--
Thank you for your contribution!
In order to reduce the number of notifications sent to the maintainers, please:
- create your PR as draft, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests,
- make sure that all of the CI checks pass,
- mark your PR as ready for review, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#marking-a-pull-request-as-ready-for-review
-->

<!-- 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.
- [x ] All new code is formatted with `clang-format -i --style=file path/to/your/file.java`

Refactored HillCipher.java to remove Scanner dependency and make it testable

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/TheAlgorithms/Java/pull/5562?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) Report
Attention: Patch coverage is `89.09091%` with `6 lines` in your changes missing coverage. Please review.
> Project coverage is 53.45%. Comparing base [(`be8df21`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/be8df2188c716389de5150487c0862b80d88bd4e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) to head [(`d229d5b`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/d229d5b3d675fb1a115531e0f79e1551f4d0457f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).

| [Files with missing lines](https://app.codecov.io/gh/TheAlgorithms/Java/pull/5562?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) | Patch % | Lines |
|---|---|---|
| [...ain/java/com/thealgorithms/ciphers/HillCipher.java](https://app.codecov.io/gh/TheAlgorithms/Java/pull/5562?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Fthealgorithms%2Fciphers%2FHillCipher.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms#diff-c3JjL21haW4vamF2YS9jb20vdGhlYWxnb3JpdGhtcy9jaXBoZXJzL0hpbGxDaXBoZXIuamF2YQ==) | 89.09% | [3 Missing and 3 partials :warning: ](https://app.codecov.io/gh/TheAlgorithms/Java/pull/5562?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #5562      +/-   ##
============================================
+ Coverage     52.95%   53.45%   +0.49%     
- Complexity     3309     3328      +19     
============================================
  Files           529      529              
  Lines         15343    15303      -40     
  Branches       2925     2917       -8     
============================================
+ Hits           8125     8180      +55     
+ Misses         6882     6785      -97     
- Partials        336      338       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/TheAlgorithms/Java/pull/5562?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).


Patch:
diff --git a/src/main/java/com/thealgorithms/ciphers/HillCipher.java b/src/main/java/com/thealgorithms/ciphers/HillCipher.java
index 780009c2..01b1aeb8 100644
--- a/src/main/java/com/thealgorithms/ciphers/HillCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/HillCipher.java
@@ -1,178 +1,103 @@
 package com.thealgorithms.ciphers;
 
-import java.util.Scanner;
+public class HillCipher {
 
-/*
- * Java Implementation of Hill Cipher
- * Hill cipher is a polyalphabetic substitution cipher. Each letter is represented by a number
- * belonging to the set Z26 where A=0 , B=1, ..... Z=25. To encrypt a message, each block of n
- * letters (since matrix size is n x n) is multiplied by an invertible n × n matrix, against
- * modulus 26. To decrypt the message, each block is multiplied by the inverse of the matrix used
- * for encryption. The cipher key and plaintext/ciphertext are user inputs.
- * @author Ojasva Jain
- */
-public final class HillCipher {
-    private HillCipher() {
-    }
-
-    static Scanner userInput = new Scanner(System.in);
-
-    /* Following function encrypts the message
-     */
-    static void encrypt(String message) {
-        message = message.toUpperCase();
-        // Get key matrix
-        System.out.println("Enter key matrix size");
-        int matrixSize = userInput.nextInt();
-        System.out.println("Enter Key/encryptionKey matrix ");
-        int[][] keyMatrix = new int[matrixSize][matrixSize];
-        for (int i = 0; i < matrixSize; i++) {
-            for (int j = 0; j < matrixSize; j++) {
-                keyMatrix[i][j] = userInput.nextInt();
-            }
-        }
-        // check if det = 0
+    // Encrypts the message using the key matrix
+    public String encrypt(String message, int[][] keyMatrix) {
+        message = message.toUpperCase().replaceAll("[^A-Z]", "");
+        int matrixSize = keyMatrix.length;
         validateDeterminant(keyMatrix, matrixSize);
 
-        int[][] messageVector = new int[matrixSize][1];
-        String cipherText = "";
-        int[][] cipherMatrix = new int[matrixSize][1];
-        int j = 0;
-        while (j < message.length()) {
+        StringBuilder cipherText = new StringBuilder();
+        int[] messageVector = new int[matrixSize];
+        int[] cipherVector = new int[matrixSize];
+        int index = 0;
+
+        while (index < message.length()) {
             for (int i = 0; i < matrixSize; i++) {
-                if (j >= message.length()) {
-                    messageVector[i][0] = 23;
+                if (index < message.length()) {
+                    messageVector[i] = message.charAt(index++) - 'A';
                 } else {
-                    messageVector[i][0] = (message.charAt(j)) % 65;
+                    messageVector[i] = 'X' - 'A'; // Padding with 'X' if needed
                 }
-                System.out.println(messageVector[i][0]);
-                j++;
             }
-            int x;
-            int i;
-            for (i = 0; i < matrixSize; i++) {
-                cipherMatrix[i][0] = 0;
 
-                for (x = 0; x < matrixSize; x++) {
-                    cipherMatrix[i][0] += keyMatrix[i][x] * messageVector[x][0];
+            for (int i = 0; i < matrixSize; i++) {
+                cipherVector[i] = 0;
+                for (int j = 0; j < matrixSize; j++) {
+                    cipherVector[i] += keyMatrix[i][j] * messageVector[j];
                 }
-                System.out.println(cipherMatrix[i][0]);
-                cipherMatrix[i][0] = cipherMatrix[i][0] % 26;
-            }
-            for (i = 0; i < matrixSize; i++) {
-                cipherText += (char) (cipherMatrix[i][0] + 65);
+                cipherVector[i] = cipherVector[i] % 26;
+                cipherText.append((char) (cipherVector[i] + 'A'));
             }
         }
-        System.out.println("Ciphertext: " + cipherText);
+
+        return cipherText.toString();
     }
 
-    // Following function decrypts a message
-    static void decrypt(String message) {
-        message = message.toUpperCase();
-        // Get key matrix
-        System.out.println("Enter key matrix size");
-        int n = userInput.nextInt();
-        System.out.println("Enter inverseKey/decryptionKey matrix ");
-        int[][] keyMatrix = new int[n][n];
-        for (int i = 0; i < n; i++) {
-            for (int j = 0; j < n; j++) {
-                keyMatrix[i][j] = userInput.nextInt();
-            }
-        }
-        // check if det = 0
-        validateDeterminant(keyMatrix, n);
+    // Decrypts the message using the inverse key matrix
+    public String decrypt(String message, int[][] inverseKeyMatrix) {
+        message = message.toUpperCase().replaceAll("[^A-Z]", "");
+        int matrixSize = inverseKeyMatrix.length;
+        validateDeterminant(inverseKeyMatrix, matrixSize);
+
+        StringBuilder plainText = new StringBuilder();
+        int[] messageVector = new int[matrixSize];
+        int[] plainVector = new int[matrixSize];
+        int index = 0;
 
-        // solving for the required plaintext message
-        int[][] messageVector = new int[n][1];
-        String plainText = "";
-        int[][] plainMatrix = new int[n][1];
-        int j = 0;
-        while (j < message.length()) {
-            for (int i = 0; i < n; i++) {
-                if (j >= message.length()) {
-                    messageVector[i][0] = 23;
+        while (index < message.length()) {
+            for (int i = 0; i < matrixSize; i++) {
+                if (index < message.length()) {
+                    messageVector[i] = message.charAt(index++) - 'A';
                 } else {
-                    messageVector[i][0] = (message.charAt(j)) % 65;
+                    messageVector[i] = 'X' - 'A'; // Padding with 'X' if needed
                 }
-                System.out.println(messageVector[i][0]);
-                j++;
             }
-            int x;
-            int i;
-            for (i = 0; i < n; i++) {
-                plainMatrix[i][0] = 0;
 
-                for (x = 0; x < n; x++) {
-                    plainMatrix[i][0] += keyMatrix[i][x] * messageVector[x][0];
+            for (int i = 0; i < matrixSize; i++) {
+                plainVector[i] = 0;
+                for (int j = 0; j < matrixSize; j++) {
+                    plainVector[i] += inverseKeyMatrix[i][j] * messageVector[j];
                 }
-
-                plainMatrix[i][0] = plainMatrix[i][0] % 26;
-            }
-            for (i = 0; i < n; i++) {
-                plainText += (char) (plainMatrix[i][0] + 65);
+                plainVector[i] = plainVector[i] % 26;
+                plainText.append((char) (plainVector[i] + 'A'));
             }
         }
-        System.out.println("Plaintext: " + plainText);
+
+        return plainText.toString();
     }
 
-    // Determinant calculator
-    public static int determinant(int[][] a, int n) {
-        int det = 0;
-        int sign = 1;
-        int p = 0;
-        int q = 0;
+    // Validates that the determinant of the key matrix is not zero modulo 26
+    private void validateDeterminant(int[][] keyMatrix, int n) {
+        int det = determinant(keyMatrix, n) % 26;
+        if (det == 0) {
+            throw new IllegalArgumentException("Invalid key matrix. Determinant is zero modulo 26.");
+        }
+    }
 
+    // Computes the determinant of a matrix recursively
+    private int determinant(int[][] matrix, int n) {
+        int det = 0;
         if (n == 1) {
-            det = a[0][0];
-        } else {
-            int[][] b = new int[n - 1][n - 1];
-            for (int x = 0; x < n; x++) {
-                p = 0;
-                q = 0;
-                for (int i = 1; i < n; i++) {
-                    for (int j = 0; j < n; j++) {
-                        if (j != x) {
-                            b[p][q++] = a[i][j];
-                            if (q % (n - 1) == 0) {
-                                p++;
-                                q = 0;
-                            }
-                        }
+            return matrix[0][0];
+        }
+        int sign = 1;
+        int[][] subMatrix = new int[n - 1][n - 1];
+        for (int x = 0; x < n; x++) {
+            int subI = 0;
+            for (int i = 1; i < n; i++) {
+                int subJ = 0;
+                for (int j = 0; j < n; j++) {
+                    if (j != x) {
+                        subMatrix[subI][subJ++] = matrix[i][j];
                     }
                 }
-                det = det + a[0][x] * determinant(b, n - 1) * sign;
-                sign = -sign;
+                subI++;
             }
+            det += sign * matrix[0][x] * determinant(subMatrix, n - 1);
+            sign = -sign;
         }
         return det;
     }
-
-    // Function to implement Hill Cipher
-    static void hillCipher(String message) {
-        System.out.println("What do you want to process from the message?");
-        System.out.println("Press 1: To Encrypt");
-        System.out.println("Press 2: To Decrypt");
-        short sc = userInput.nextShort();
-        if (sc == 1) {
-            encrypt(message);
-        } else if (sc == 2) {
-            decrypt(message);
-        } else {
-            System.out.println("Invalid input, program terminated.");
-        }
-    }
-
-    static void validateDeterminant(int[][] keyMatrix, int n) {
-        if (determinant(keyMatrix, n) % 26 == 0) {
-            System.out.println("Invalid key, as determinant = 0. Program Terminated");
-        }
-    }
-
-    // Driver code
-    public static void main(String[] args) {
-        // Get the message to be encrypted
-        System.out.println("Enter message");
-        String message = userInput.nextLine();
-        hillCipher(message);
-    }
 }


Test patch:
diff --git a/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java b/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java
new file mode 100644
index 00000000..8121b617
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class HillCipherTest {
+
+    HillCipher hillCipher = new HillCipher();
+
+    @Test
+    void hillCipherEncryptTest() {
+        // given
+        String message = "ACT"; // Plaintext message
+        int[][] keyMatrix = {{6, 24, 1}, {13, 16, 10}, {20, 17, 15}}; // Encryption key matrix
+
+        // when
+        String cipherText = hillCipher.encrypt(message, keyMatrix);
+
+        // then
+        assertEquals("POH", cipherText);
+    }
+
+    @Test
+    void hillCipherDecryptTest() {
+        // given
+        String cipherText = "POH"; // Ciphertext message
+        int[][] inverseKeyMatrix = {{8, 5, 10}, {21, 8, 21}, {21, 12, 8}}; // Decryption (inverse key) matrix
+
+        // when
+        String plainText = hillCipher.decrypt(cipherText, inverseKeyMatrix);
+
+        // then
+        assertEquals("ACT", plainText);
+    }
+}