Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 0520a2358ba4..ab12e8ec4082 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
org.junit
junit-bom
- 5.11.1
+ 5.11.2
pom
import
From ce345956288d2d304ff07f30e154c33832f651eb Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sat, 5 Oct 2024 15:17:52 +0530
Subject: [PATCH 071/457] Improve `TrieImp.java` comments & enhance readability
(#5526)
---
DIRECTORY.md | 27 ++--
.../datastructures/trees/TrieImp.java | 130 +++++++++---------
.../datastructures/trees/TrieImpTest.java | 76 ++++++++++
3 files changed, 156 insertions(+), 77 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index c272d3865b58..fbd3b01c6321 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -22,7 +22,7 @@
* [WordSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/WordSearch.java)
* bitmanipulation
* [BitSwap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java)
- * [CountSetBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/CountSetBits.java)
+ * [CountSetBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java)
* [HighestSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java)
* [IndexOfRightMostSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java)
* [IsEven](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsEven.java)
@@ -69,7 +69,7 @@
* [HexaDecimalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java)
* [HexaDecimalToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java)
* [HexToOct](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexToOct.java)
- * [IntegerToEnglish] (https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java)
+ * [IntegerToEnglish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java)
* [IntegerToRoman](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java)
* [OctalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToBinary.java)
* [OctalToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToDecimal.java)
@@ -410,7 +410,7 @@
* [WordBoggle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/WordBoggle.java)
* others
* [ArrayLeftRotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ArrayLeftRotation.java)
- * [ArrayRightRotation](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ArrayRightRotation.java)
+ * [ArrayRightRotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ArrayRightRotation.java)
* [BankersAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/BankersAlgorithm.java)
* [BFPRT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/BFPRT.java)
* [BoyerMoore](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/BoyerMoore.java)
@@ -559,8 +559,8 @@
* [CharactersSame](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CharactersSame.java)
* [CheckAnagrams](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CheckAnagrams.java)
* [CheckVowels](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CheckVowels.java)
- * [CountChar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/CountChar.java)
- * [CountWords](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/CountWords.java)
+ * [CountChar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CountChar.java)
+ * [CountWords](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CountWords.java)
* [HammingDistance](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/HammingDistance.java)
* [HorspoolSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/HorspoolSearch.java)
* [Isomorphic](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Isomorphic.java)
@@ -575,13 +575,13 @@
* [Pangram](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Pangram.java)
* [PermuteString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/PermuteString.java)
* [RabinKarp](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/RabinKarp.java)
- * [ReturnSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ReturnSubsequence.java)
+ * [ReturnSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReturnSubsequence.java)
* [ReverseString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReverseString.java)
* [ReverseStringRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReverseStringRecursive.java)
* [ReverseWordsInString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReverseWordsInString.java)
* [Rotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Rotation.java)
* [StringCompression](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/StringCompression.java)
- * [StringMatchFiniteAutomata](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/StringMatchFiniteAutomata.java)
+ * [StringMatchFiniteAutomata](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/StringMatchFiniteAutomata.java)
* [Upper](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Upper.java)
* [ValidParentheses](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ValidParentheses.java)
* [WordLadder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/WordLadder.java)
@@ -606,7 +606,7 @@
* [WordSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/WordSearchTest.java)
* bitmanipulation
* [BitSwapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java)
- * [CountSetBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/CountSetBitsTest.java)
+ * [CountSetBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java)
* [HighestSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/HighestSetBitTest.java)
* [IndexOfRightMostSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBitTest.java)
* [IsEvenTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsEvenTest.java)
@@ -621,6 +621,7 @@
* [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java)
* [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java)
* [DESTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DESTest.java)
+ * [HillCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java)
* [PlayfairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java)
* [PolybiusTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PolybiusTest.java)
* [RSATest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RSATest.java)
@@ -639,6 +640,7 @@
* [HexaDecimalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java)
* [HexaDecimalToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java)
* [HexToOctTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexToOctTest.java)
+ * [IntegerToEnglishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java)
* [IntegerToRomanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java)
* [OctalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java)
* [OctalToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToDecimalTest.java)
@@ -727,6 +729,7 @@
* [SameTreesCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SameTreesCheckTest.java)
* [SplayTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SplayTreeTest.java)
* [TreeTestUtils](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreeTestUtils.java)
+ * [TrieImpTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java)
* [VerticalOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversalTest.java)
* [ZigzagTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/ZigzagTraversalTest.java)
* divideandconquer
@@ -991,8 +994,8 @@
* [CharacterSameTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CharacterSameTest.java)
* [CheckAnagramsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java)
* [CheckVowelsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CheckVowelsTest.java)
- * [CountCharTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/CountCharTest.java)
- * [CountWordsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/CountWordsTest.java)
+ * [CountCharTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CountCharTest.java)
+ * [CountWordsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CountWordsTest.java)
* [HammingDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/HammingDistanceTest.java)
* [HorspoolSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java)
* [IsomorphicTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/IsomorphicTest.java)
@@ -1005,13 +1008,13 @@
* [PalindromeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/PalindromeTest.java)
* [PangramTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/PangramTest.java)
* [PermuteStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/PermuteStringTest.java)
- * [ReturnSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ReturnSubsequenceTest.java)
+ * [ReturnSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReturnSubsequenceTest.java)
* [ReverseStringRecursiveTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReverseStringRecursiveTest.java)
* [ReverseStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReverseStringTest.java)
* [ReverseWordsInStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReverseWordsInStringTest.java)
* [RotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/RotationTest.java)
* [StringCompressionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/StringCompressionTest.java)
- * [StringMatchFiniteAutomataTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/StringMatchFiniteAutomataTest.java)
+ * [StringMatchFiniteAutomataTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/StringMatchFiniteAutomataTest.java)
* [UpperTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/UpperTest.java)
* [ValidParenthesesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java)
* [WordLadderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/WordLadderTest.java)
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java b/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java
index d166653ff1b4..a43a454146cb 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java
@@ -1,19 +1,35 @@
package com.thealgorithms.datastructures.trees;
-import java.util.Scanner;
-
/**
- * Trie Data structure implementation without any libraries
+ * Trie Data structure implementation without any libraries.
+ *
+ * The Trie (also known as a prefix tree) is a special tree-like data structure
+ * that is used to store a dynamic set or associative array where the keys are
+ * usually strings. It is highly efficient for prefix-based searches.
+ *
+ * This implementation supports basic Trie operations like insertion, search,
+ * and deletion.
+ *
+ * Each node of the Trie represents a character and has child nodes for each
+ * possible character.
*
* @author Dheeraj Kumar Barnwal
*/
public class TrieImp {
+ /**
+ * Represents a Trie Node that stores a character and pointers to its children.
+ * Each node has an array of 26 children (one for each letter from 'a' to 'z').
+ */
public class TrieNode {
TrieNode[] child;
boolean end;
+ /**
+ * Constructor to initialize a TrieNode with an empty child array
+ * and set end to false.
+ */
public TrieNode() {
child = new TrieNode[26];
end = false;
@@ -22,10 +38,22 @@ public TrieNode() {
private final TrieNode root;
+ /**
+ * Constructor to initialize the Trie.
+ * The root node is created but doesn't represent any character.
+ */
public TrieImp() {
root = new TrieNode();
}
+ /**
+ * Inserts a word into the Trie.
+ *
+ * The method traverses the Trie from the root, character by character, and adds
+ * nodes if necessary. It marks the last node of the word as an end node.
+ *
+ * @param word The word to be inserted into the Trie.
+ */
public void insert(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
@@ -39,6 +67,16 @@ public void insert(String word) {
currentNode.end = true;
}
+ /**
+ * Searches for a word in the Trie.
+ *
+ * This method traverses the Trie based on the input word and checks whether
+ * the word exists. It returns true if the word is found and its end flag is
+ * true.
+ *
+ * @param word The word to search in the Trie.
+ * @return true if the word exists in the Trie, false otherwise.
+ */
public boolean search(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
@@ -52,6 +90,17 @@ public boolean search(String word) {
return currentNode.end;
}
+ /**
+ * Deletes a word from the Trie.
+ *
+ * The method traverses the Trie to find the word and marks its end flag as
+ * false.
+ * It returns true if the word was successfully deleted, false if the word
+ * wasn't found.
+ *
+ * @param word The word to be deleted from the Trie.
+ * @return true if the word was found and deleted, false if it was not found.
+ */
public boolean delete(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
@@ -69,75 +118,26 @@ public boolean delete(String word) {
return false;
}
+ /**
+ * Helper method to print a string to the console.
+ *
+ * @param print The string to be printed.
+ */
public static void sop(String print) {
System.out.println(print);
}
/**
- * Regex to check if word contains only a-z character
+ * Validates if a given word contains only lowercase alphabetic characters
+ * (a-z).
+ *
+ * The method uses a regular expression to check if the word matches the pattern
+ * of only lowercase letters.
+ *
+ * @param word The word to be validated.
+ * @return true if the word is valid (only a-z), false otherwise.
*/
public static boolean isValid(String word) {
return word.matches("^[a-z]+$");
}
-
- public static void main(String[] args) {
- TrieImp obj = new TrieImp();
- String word;
- @SuppressWarnings("resource") Scanner scan = new Scanner(System.in);
- sop("string should contain only a-z character for all operation");
- while (true) {
- sop("1. Insert\n2. Search\n3. Delete\n4. Quit");
- try {
- int t = scan.nextInt();
- switch (t) {
- case 1:
- word = scan.next();
- if (isValid(word)) {
- obj.insert(word);
- } else {
- sop("Invalid string: allowed only a-z");
- }
- break;
- case 2:
- word = scan.next();
- boolean resS = false;
- if (isValid(word)) {
- resS = obj.search(word);
- } else {
- sop("Invalid string: allowed only a-z");
- }
- if (resS) {
- sop("word found");
- } else {
- sop("word not found");
- }
- break;
- case 3:
- word = scan.next();
- boolean resD = false;
- if (isValid(word)) {
- resD = obj.delete(word);
- } else {
- sop("Invalid string: allowed only a-z");
- }
- if (resD) {
- sop("word got deleted successfully");
- } else {
- sop("word not found");
- }
- break;
- case 4:
- sop("Quit successfully");
- System.exit(1);
- break;
- default:
- sop("Input int from 1-4");
- break;
- }
- } catch (Exception e) {
- String badInput = scan.next();
- sop("This is bad input: " + badInput);
- }
- }
- }
}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java b/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java
new file mode 100644
index 000000000000..600fdef0a718
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java
@@ -0,0 +1,76 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TrieImpTest {
+ private TrieImp trie;
+
+ @BeforeEach
+ public void setUp() {
+ trie = new TrieImp();
+ }
+
+ @Test
+ public void testInsertAndSearchBasic() {
+ String word = "hello";
+ trie.insert(word);
+ assertTrue(trie.search(word), "Search should return true for an inserted word.");
+ }
+
+ @Test
+ public void testSearchNonExistentWord() {
+ String word = "world";
+ assertFalse(trie.search(word), "Search should return false for a non-existent word.");
+ }
+
+ @Test
+ public void testInsertAndSearchMultipleWords() {
+ String word1 = "cat";
+ String word2 = "car";
+ trie.insert(word1);
+ trie.insert(word2);
+
+ assertTrue(trie.search(word1), "Search should return true for an inserted word.");
+ assertTrue(trie.search(word2), "Search should return true for another inserted word.");
+ assertFalse(trie.search("dog"), "Search should return false for a word not in the Trie.");
+ }
+
+ @Test
+ public void testDeleteExistingWord() {
+ String word = "remove";
+ trie.insert(word);
+ assertTrue(trie.delete(word), "Delete should return true for an existing word.");
+ assertFalse(trie.search(word), "Search should return false after deletion.");
+ }
+
+ @Test
+ public void testDeleteNonExistentWord() {
+ String word = "nonexistent";
+ assertFalse(trie.delete(word), "Delete should return false for a non-existent word.");
+ }
+
+ @Test
+ public void testInsertAndSearchPrefix() {
+ String prefix = "pre";
+ String word = "prefix";
+ trie.insert(prefix);
+ trie.insert(word);
+
+ assertTrue(trie.search(prefix), "Search should return true for an inserted prefix.");
+ assertTrue(trie.search(word), "Search should return true for a word with the prefix.");
+ assertFalse(trie.search("pref"), "Search should return false for a prefix that is not a full word.");
+ }
+
+ @Test
+ public void testIsValidWord() {
+ assertTrue(TrieImp.isValid("validword"), "Word should be valid (only lowercase letters).");
+ assertFalse(TrieImp.isValid("InvalidWord"), "Word should be invalid (contains uppercase letters).");
+ assertFalse(TrieImp.isValid("123abc"), "Word should be invalid (contains numbers).");
+ assertFalse(TrieImp.isValid("hello!"), "Word should be invalid (contains special characters).");
+ assertFalse(TrieImp.isValid(""), "Empty string should be invalid.");
+ }
+}
From 339027388e20297396196d58b19b03b9322efb3f Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sat, 5 Oct 2024 18:09:22 +0530
Subject: [PATCH 072/457] Improve comments in `SumOfSubset.java` (#5514)
---
.../dynamicprogramming/SumOfSubset.java | 27 ++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SumOfSubset.java b/src/main/java/com/thealgorithms/dynamicprogramming/SumOfSubset.java
index dd48008bd21e..e9c15c1b4f24 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/SumOfSubset.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/SumOfSubset.java
@@ -1,9 +1,35 @@
package com.thealgorithms.dynamicprogramming;
+/**
+ * A utility class that contains the Sum of Subset problem solution using
+ * recursion.
+ *
+ * The Sum of Subset problem determines whether a subset of elements from a
+ * given array sums up to a specific target value.
+ *
+ * Wikipedia: https://en.wikipedia.org/wiki/Subset_sum_problem
+ */
public final class SumOfSubset {
+
private SumOfSubset() {
}
+ /**
+ * Determines if there exists a subset of elements in the array `arr` that
+ * adds up to the given `key` value using recursion.
+ *
+ * @param arr The array of integers.
+ * @param num The index of the current element being considered.
+ * @param key The target sum we are trying to achieve.
+ * @return true if a subset of `arr` adds up to `key`, false otherwise.
+ *
+ * This is a recursive solution that checks for two possibilities at
+ * each step:
+ * 1. Include the current element in the subset and check if the
+ * remaining elements can sum up to the remaining target.
+ * 2. Exclude the current element and check if the remaining elements
+ * can sum up to the target without this element.
+ */
public static boolean subsetSum(int[] arr, int num, int key) {
if (key == 0) {
return true;
@@ -14,7 +40,6 @@ public static boolean subsetSum(int[] arr, int num, int key) {
boolean include = subsetSum(arr, num - 1, key - arr[num]);
boolean exclude = subsetSum(arr, num - 1, key);
-
return include || exclude;
}
}
From f34fe4d8408ac35cef900cfae022ffadfc007ea3 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sat, 5 Oct 2024 18:13:42 +0530
Subject: [PATCH 073/457] Enhance comments & improve readability in
`LongestCommonSubsequence.java` (#5523)
---
DIRECTORY.md | 1 +
.../LongestCommonSubsequence.java | 67 +++++++++-----
.../LongestCommonSubsequenceTest.java | 89 +++++++++++++++++++
3 files changed, 136 insertions(+), 21 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index fbd3b01c6321..1bad5d3b98a3 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -747,6 +747,7 @@
* [LevenshteinDistanceTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LevenshteinDistanceTests.java)
* [LongestAlternatingSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestAlternatingSubsequenceTest.java)
* [LongestArithmeticSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequenceTest.java)
+ * [LongestCommonSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java)
* [LongestIncreasingSubsequenceTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceTests.java)
* [LongestPalindromicSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubstringTest.java)
* [LongestValidParenthesesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestValidParenthesesTest.java)
diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequence.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequence.java
index 2d1fa1d1153f..54837b5f4e71 100644
--- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequence.java
+++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequence.java
@@ -1,73 +1,98 @@
package com.thealgorithms.dynamicprogramming;
+/**
+ * This class implements the Longest Common Subsequence (LCS) problem.
+ * The LCS of two sequences is the longest sequence that appears in both
+ * sequences
+ * in the same order, but not necessarily consecutively.
+ *
+ * This implementation uses dynamic programming to find the LCS of two strings.
+ */
final class LongestCommonSubsequence {
+
private LongestCommonSubsequence() {
}
+ /**
+ * Returns the Longest Common Subsequence (LCS) of two given strings.
+ *
+ * @param str1 The first string.
+ * @param str2 The second string.
+ * @return The LCS of the two strings, or null if one of the strings is null.
+ */
public static String getLCS(String str1, String str2) {
- // At least one string is null
+ // If either string is null, return null as LCS can't be computed.
if (str1 == null || str2 == null) {
return null;
}
-
- // At least one string is empty
+ // If either string is empty, return an empty string as LCS.
if (str1.length() == 0 || str2.length() == 0) {
return "";
}
+ // Convert the strings into arrays of characters
String[] arr1 = str1.split("");
String[] arr2 = str2.split("");
- // lcsMatrix[i][j] = LCS of first i elements of arr1 and first j characters of arr2
+ // lcsMatrix[i][j] = LCS(first i characters of str1, first j characters of str2)
int[][] lcsMatrix = new int[arr1.length + 1][arr2.length + 1];
+ // Base Case: Fill the LCS matrix 0th row & 0th column with 0s
+ // as LCS of any string with an empty string is 0.
for (int i = 0; i < arr1.length + 1; i++) {
lcsMatrix[i][0] = 0;
}
for (int j = 1; j < arr2.length + 1; j++) {
lcsMatrix[0][j] = 0;
}
+
+ // Build the LCS matrix by comparing characters of str1 & str2
for (int i = 1; i < arr1.length + 1; i++) {
for (int j = 1; j < arr2.length + 1; j++) {
+ // If characters match, the LCS increases by 1
if (arr1[i - 1].equals(arr2[j - 1])) {
lcsMatrix[i][j] = lcsMatrix[i - 1][j - 1] + 1;
} else {
+ // Otherwise, take the maximum of the left or above values
lcsMatrix[i][j] = Math.max(lcsMatrix[i - 1][j], lcsMatrix[i][j - 1]);
}
}
}
+
+ // Call helper function to reconstruct the LCS from the matrix
return lcsString(str1, str2, lcsMatrix);
}
+ /**
+ * Reconstructs the LCS string from the LCS matrix.
+ *
+ * @param str1 The first string.
+ * @param str2 The second string.
+ * @param lcsMatrix The matrix storing the lengths of LCSs
+ * of substrings of str1 and str2.
+ * @return The LCS string.
+ */
public static String lcsString(String str1, String str2, int[][] lcsMatrix) {
- StringBuilder lcs = new StringBuilder();
- int i = str1.length();
- int j = str2.length();
+ StringBuilder lcs = new StringBuilder(); // Hold the LCS characters.
+ int i = str1.length(); // Start from the end of str1.
+ int j = str2.length(); // Start from the end of str2.
+
+ // Trace back through the LCS matrix to reconstruct the LCS
while (i > 0 && j > 0) {
+ // If characters match, add to the LCS and move diagonally in the matrix
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
lcs.append(str1.charAt(i - 1));
i--;
j--;
} else if (lcsMatrix[i - 1][j] > lcsMatrix[i][j - 1]) {
+ // If the value above is larger, move up
i--;
} else {
+ // If the value to the left is larger, move left
j--;
}
}
- return lcs.reverse().toString();
- }
- public static void main(String[] args) {
- String str1 = "DSGSHSRGSRHTRD";
- String str2 = "DATRGAGTSHS";
- String lcs = getLCS(str1, str2);
-
- // Print LCS
- if (lcs != null) {
- System.out.println("String 1: " + str1);
- System.out.println("String 2: " + str2);
- System.out.println("LCS: " + lcs);
- System.out.println("LCS length: " + lcs.length());
- }
+ return lcs.reverse().toString(); // LCS built in reverse, so reverse it back
}
}
diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
new file mode 100644
index 000000000000..40bbdff15ca6
--- /dev/null
+++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java
@@ -0,0 +1,89 @@
+package com.thealgorithms.dynamicprogramming;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class LongestCommonSubsequenceTest {
+
+ @Test
+ public void testLCSBasic() {
+ String str1 = "ABCBDAB";
+ String str2 = "BDCAB";
+ String expected = "BDAB"; // The longest common subsequence
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSIdenticalStrings() {
+ String str1 = "AGGTAB";
+ String str2 = "AGGTAB";
+ String expected = "AGGTAB"; // LCS is the same as the strings
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSNoCommonCharacters() {
+ String str1 = "ABC";
+ String str2 = "XYZ";
+ String expected = ""; // No common subsequence
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSWithEmptyString() {
+ String str1 = "";
+ String str2 = "XYZ";
+ String expected = ""; // LCS with an empty string should be empty
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSWithBothEmptyStrings() {
+ String str1 = "";
+ String str2 = "";
+ String expected = ""; // LCS with both strings empty should be empty
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSWithNullFirstString() {
+ String str1 = null;
+ String str2 = "XYZ";
+ String expected = null; // Should return null if first string is null
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSWithNullSecondString() {
+ String str1 = "ABC";
+ String str2 = null;
+ String expected = null; // Should return null if second string is null
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSWithNullBothStrings() {
+ String str1 = null;
+ String str2 = null;
+ String expected = null; // Should return null if both strings are null
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testLCSWithLongerStringContainingCommonSubsequence() {
+ String str1 = "ABCDEF";
+ String str2 = "AEBDF";
+ String expected = "ABDF"; // Common subsequence is "ABDF"
+ String result = LongestCommonSubsequence.getLCS(str1, str2);
+ assertEquals(expected, result);
+ }
+}
From 07cb6c46a8d3b46d6b32c84e23da50ac8fecd30e Mon Sep 17 00:00:00 2001
From: Benjamin Burstein <98127047+bennybebo@users.noreply.github.com>
Date: Sun, 6 Oct 2024 01:37:56 -0400
Subject: [PATCH 074/457] Add autokey cipher (#5569)
---
.../com/thealgorithms/ciphers/Autokey.java | 55 +++++++++++++++++++
.../thealgorithms/ciphers/AutokeyTest.java | 36 ++++++++++++
2 files changed, 91 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/ciphers/Autokey.java
create mode 100644 src/test/java/com/thealgorithms/ciphers/AutokeyTest.java
diff --git a/src/main/java/com/thealgorithms/ciphers/Autokey.java b/src/main/java/com/thealgorithms/ciphers/Autokey.java
new file mode 100644
index 000000000000..bb67f512accf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/ciphers/Autokey.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.ciphers;
+
+/**
+ * The Autokey Cipher is an interesting and historically significant encryption method,
+ * as it improves upon the classic Vigenère Cipher by using the plaintext itself to
+ * extend the key. This makes it harder to break using frequency analysis, as it
+ * doesn’t rely solely on a repeated key.
+ * https://en.wikipedia.org/wiki/Autokey_cipher
+ *
+ * @author bennybebo
+ */
+public class Autokey {
+
+ // Encrypts the plaintext using the Autokey cipher
+ public String encrypt(String plaintext, String keyword) {
+ plaintext = plaintext.toUpperCase().replaceAll("[^A-Z]", ""); // Sanitize input
+ keyword = keyword.toUpperCase();
+
+ StringBuilder extendedKey = new StringBuilder(keyword);
+ extendedKey.append(plaintext); // Extend key with plaintext
+
+ StringBuilder ciphertext = new StringBuilder();
+
+ for (int i = 0; i < plaintext.length(); i++) {
+ char plainChar = plaintext.charAt(i);
+ char keyChar = extendedKey.charAt(i);
+
+ int encryptedChar = (plainChar - 'A' + keyChar - 'A') % 26 + 'A';
+ ciphertext.append((char) encryptedChar);
+ }
+
+ return ciphertext.toString();
+ }
+
+ // Decrypts the ciphertext using the Autokey cipher
+ public String decrypt(String ciphertext, String keyword) {
+ ciphertext = ciphertext.toUpperCase().replaceAll("[^A-Z]", ""); // Sanitize input
+ keyword = keyword.toUpperCase();
+
+ StringBuilder plaintext = new StringBuilder();
+ StringBuilder extendedKey = new StringBuilder(keyword);
+
+ for (int i = 0; i < ciphertext.length(); i++) {
+ char cipherChar = ciphertext.charAt(i);
+ char keyChar = extendedKey.charAt(i);
+
+ int decryptedChar = (cipherChar - 'A' - (keyChar - 'A') + 26) % 26 + 'A';
+ plaintext.append((char) decryptedChar);
+
+ extendedKey.append((char) decryptedChar); // Extend key with each decrypted char
+ }
+
+ return plaintext.toString();
+ }
+}
diff --git a/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java b/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java
new file mode 100644
index 000000000000..52ecff7cdeee
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java
@@ -0,0 +1,36 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class AutokeyCipherTest {
+
+ Autokey autokeyCipher = new Autokey();
+
+ @Test
+ void autokeyEncryptTest() {
+ // given
+ String plaintext = "MEET AT DAWN";
+ String keyword = "QUEEN";
+
+ // when
+ String cipherText = autokeyCipher.encrypt(plaintext, keyword);
+
+ // then
+ assertEquals("CYIXNFHEPN", cipherText);
+ }
+
+ @Test
+ void autokeyDecryptTest() {
+ // given
+ String ciphertext = "CYIX NF HEPN";
+ String keyword = "QUEEN";
+
+ // when
+ String plainText = autokeyCipher.decrypt(ciphertext, keyword);
+
+ // then
+ assertEquals("MEETATDAWN", plainText);
+ }
+}
From 4008e4967c52df999260757f4404ba85a7ab11c6 Mon Sep 17 00:00:00 2001
From: ShreeHarish <72642111+ShreeHarish@users.noreply.github.com>
Date: Sun, 6 Oct 2024 12:31:54 +0530
Subject: [PATCH 075/457] Add treap class (#5563)
---
.../datastructures/trees/Treap.java | 357 ++++++++++++++++++
.../datastructures/trees/TreapTest.java | 62 +++
2 files changed, 419 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/datastructures/trees/Treap.java
create mode 100644 src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/Treap.java b/src/main/java/com/thealgorithms/datastructures/trees/Treap.java
new file mode 100644
index 000000000000..1e5d551cc40b
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/trees/Treap.java
@@ -0,0 +1,357 @@
+package com.thealgorithms.datastructures.trees;
+
+import java.util.Random;
+
+/**
+ * Treap -> Tree + Heap
+ * Also called as cartesian tree
+ *
+ * @see
+ *
+ */
+
+public class Treap {
+
+ public static class TreapNode {
+ /**
+ * TreapNode class defines the individual nodes in the Treap
+ *
+ * value -> holds the value of the node.
+ * Binary Search Tree is built based on value.
+ *
+ * priority -> holds the priority of the node.
+ * Heaps are maintained based on priority.
+ * It is randomly assigned
+ *
+ * size -> holds the size of the subtree with current node as root
+ *
+ * left -> holds the left subtree
+ * right -> holds the right subtree
+ */
+ public int value;
+ private int priority;
+ private int size;
+ public TreapNode left;
+ public TreapNode right;
+
+ public TreapNode(int valueParam, int priorityParam) {
+ value = valueParam;
+ priority = priorityParam;
+ size = 1;
+ left = null;
+ right = null;
+ }
+
+ /**
+ * updateSize -> updates the subtree size of the current node
+ */
+ private void updateSize() {
+ size = 1;
+ if (left != null) {
+ size += left.size;
+ }
+ if (right != null) {
+ size += right.size;
+ }
+ }
+ }
+
+ /**
+ * root -> holds the root node in the Treap
+ * random -> to generate random priority for the nodes in the Treap
+ */
+ private TreapNode root;
+ private Random random = new Random();
+
+ /**
+ * Constructors
+ *
+ * Treap() -> create an empty Treap
+ * Treap(int[] nodeValues) -> add the elements given in the array to the Treap
+ */
+ public Treap() {
+ root = null;
+ }
+
+ /**
+ * merges two Treaps left and right into a single Treap
+ *
+ * @param left left Treap
+ * @param right right Treap
+ * @return root of merged Treap
+ */
+ private TreapNode merge(TreapNode left, TreapNode right) {
+ if (left == null) {
+ return right;
+ }
+ if (right == null) {
+ return left;
+ }
+
+ if (left.priority > right.priority) {
+ left.right = merge(left.right, right);
+ left.updateSize();
+ return left;
+ } else {
+ right.left = merge(left, right.left);
+ right.updateSize();
+ return right;
+ }
+ }
+
+ /**
+ * split the Treap into two Treaps where left Treap has nodes <= key and right Treap has nodes > key
+ *
+ * @param node root node to be split
+ * @param key key to compare the nodes
+ * @return TreapNode array of size 2.
+ * TreapNode[0] contains the root of left Treap after split
+ * TreapNode[1] contains the root of right Treap after split
+ */
+ private TreapNode[] split(TreapNode node, int key) {
+ if (node == null) {
+ return new TreapNode[] {null, null};
+ }
+
+ TreapNode[] result;
+
+ if (node.value <= key) {
+ result = split(node.right, key);
+ node.right = result[0];
+ node.updateSize();
+ result[0] = node;
+ } else {
+ result = split(node.left, key);
+ node.left = result[1];
+ node.updateSize();
+ result[1] = node;
+ }
+
+ return result;
+ }
+
+ /**
+ * insert a node into the Treap
+ *
+ * @param value value to be inserted into the Treap
+ * @return root of the Treap where the value is inserted
+ */
+ public TreapNode insert(int value) {
+ if (root == null) {
+ root = new TreapNode(value, random.nextInt());
+ return root;
+ }
+
+ TreapNode[] splitted = split(root, value);
+
+ TreapNode node = new TreapNode(value, random.nextInt());
+
+ TreapNode tempMerged = merge(splitted[0], node);
+ tempMerged.updateSize();
+
+ TreapNode merged = merge(tempMerged, splitted[1]);
+ merged.updateSize();
+
+ root = merged;
+
+ return root;
+ }
+
+ /**
+ * delete a value from root if present
+ *
+ * @param value value to be deleted from the Treap
+ * @return root of the Treap where delete has been performed
+ */
+ public TreapNode delete(int value) {
+ root = deleteNode(root, value);
+ return root;
+ }
+
+ private TreapNode deleteNode(TreapNode root, int value) {
+ if (root == null) {
+ return null;
+ }
+
+ if (value < root.value) {
+ root.left = deleteNode(root.left, value);
+ } else if (value > root.value) {
+ root.right = deleteNode(root.right, value);
+ } else {
+ root = merge(root.left, root.right);
+ }
+
+ if (root != null) {
+ root.updateSize();
+ }
+ return root;
+ }
+
+ /**
+ * print inorder traversal of the Treap
+ */
+ public void inOrder() {
+ System.out.print("{");
+ printInorder(root);
+ System.out.print("}");
+ }
+
+ private void printInorder(TreapNode root) {
+ if (root == null) {
+ return;
+ }
+ printInorder(root.left);
+ System.out.print(root.value + ",");
+ printInorder(root.right);
+ }
+
+ /**
+ * print preOrder traversal of the Treap
+ */
+ public void preOrder() {
+ System.out.print("{");
+ printPreOrder(root);
+ System.out.print("}");
+ }
+
+ private void printPreOrder(TreapNode root) {
+ if (root == null) {
+ return;
+ }
+ System.out.print(root.value + ",");
+ printPreOrder(root.left);
+ printPreOrder(root.right);
+ }
+
+ /**
+ * print postOrder traversal of the Treap
+ */
+ public void postOrder() {
+ System.out.print("{");
+ printPostOrder(root);
+ System.out.print("}");
+ }
+
+ private void printPostOrder(TreapNode root) {
+ if (root == null) {
+ return;
+ }
+ printPostOrder(root.left);
+ printPostOrder(root.right);
+ System.out.print(root.value + ",");
+ }
+
+ /**
+ * Search a value in the Treap
+ *
+ * @param value value to be searched for
+ * @return node containing the value
+ * null if not found
+ */
+ public TreapNode search(int value) {
+ return searchVal(root, value);
+ }
+
+ private TreapNode searchVal(TreapNode root, int value) {
+ if (root == null) {
+ return null;
+ }
+
+ if (root.value == value) {
+ return root;
+ } else if (root.value < value) {
+ return searchVal(root.right, value);
+ } else {
+ return searchVal(root.left, value);
+ }
+ }
+
+ /**
+ * find the lowerBound of a value in the Treap
+ *
+ * @param value value for which lowerBound is to be found
+ * @return node which is the lowerBound of the value passed
+ */
+ public TreapNode lowerBound(int value) {
+ TreapNode lowerBoundNode = null;
+ TreapNode current = root;
+
+ while (current != null) {
+ if (current.value >= value) {
+ lowerBoundNode = current;
+ current = current.left;
+ } else {
+ current = current.right;
+ }
+ }
+
+ return lowerBoundNode;
+ }
+
+ /**
+ * find the upperBound of a value in the Treap
+ *
+ * @param value value for which upperBound is to be found
+ * @return node which is the upperBound of the value passed
+ */
+ public TreapNode upperBound(int value) {
+ TreapNode upperBoundNode = null;
+ TreapNode current = root;
+
+ while (current != null) {
+ if (current.value > value) {
+ upperBoundNode = current;
+ current = current.left;
+ } else {
+ current = current.right;
+ }
+ }
+
+ return upperBoundNode;
+ }
+
+ /**
+ * returns size of the Treap
+ */
+ public int size() {
+ if (root == null) {
+ return 0;
+ }
+ return root.size;
+ }
+
+ /**
+ * returns if Treap is empty
+ */
+ public boolean isEmpty() {
+ return root == null;
+ }
+
+ /**
+ * returns root node of the Treap
+ */
+ public TreapNode getRoot() {
+ return root;
+ }
+
+ /**
+ * returns left node of the TreapNode
+ */
+ public TreapNode getLeft(TreapNode node) {
+ return node.left;
+ }
+
+ /**
+ * returns the right node of the TreapNode
+ */
+ public TreapNode getRight(TreapNode node) {
+ return node.right;
+ }
+
+ /**
+ * prints the value, priority, size of the subtree of the TreapNode, left TreapNode and right TreapNode of the node
+ */
+ public String toString(TreapNode node) {
+ return "{value : " + node.value + ", priority : " + node.priority + ", subTreeSize = " + node.size + ", left = " + node.left + ", right = " + node.right + "}";
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
new file mode 100644
index 000000000000..09ada594faca
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java
@@ -0,0 +1,62 @@
+package com.thealgorithms.datastructures.trees;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import org.junit.jupiter.api.Test;
+
+public class TreapTest {
+
+ @Test
+ public void searchAndFound() {
+ Treap treap = new Treap();
+ treap.insert(5);
+ treap.insert(9);
+ treap.insert(6);
+ treap.insert(2);
+ treap.insert(3);
+ treap.insert(8);
+ treap.insert(1);
+ assertEquals(5, treap.search(5).value);
+ }
+
+ @Test
+ public void searchAndNotFound() {
+ Treap treap = new Treap();
+ treap.insert(5);
+ treap.insert(9);
+ treap.insert(6);
+ treap.insert(2);
+ treap.insert(3);
+ treap.insert(8);
+ treap.insert(1);
+ assertEquals(null, treap.search(4));
+ }
+
+ @Test
+ public void lowerBound() {
+ Treap treap = new Treap();
+ treap.insert(5);
+ treap.insert(9);
+ treap.insert(6);
+ treap.insert(2);
+ treap.insert(3);
+ treap.insert(8);
+ treap.insert(1);
+ assertEquals(5, treap.lowerBound(4).value);
+ }
+
+ @Test
+ public void size() {
+ Treap treap = new Treap();
+ treap.insert(5);
+ treap.insert(9);
+ treap.insert(6);
+ treap.insert(2);
+ treap.insert(3);
+ treap.insert(8);
+ treap.insert(1);
+ assertEquals(7, treap.size());
+ assertFalse(treap.isEmpty());
+ }
+}
From 1feceb7d11bb344c5022c5ae84a652370e737f30 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sun, 6 Oct 2024 12:42:11 +0530
Subject: [PATCH 076/457] Add MLFQ Scheduler (#5575)
---
DIRECTORY.md | 6 +
.../scheduling/MLFQScheduler.java | 148 ++++++++++++++++++
.../scheduling/MLFQSchedulerTest.java | 46 ++++++
3 files changed, 200 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java
create mode 100644 src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 1bad5d3b98a3..d93dde3dffb6 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -42,6 +42,7 @@
* [AES](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AES.java)
* [AESEncryption](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AESEncryption.java)
* [AffineCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AffineCipher.java)
+ * [Autokey](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Autokey.java)
* [Blowfish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Blowfish.java)
* [Caesar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Caesar.java)
* [ColumnarTranspositionCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java)
@@ -203,6 +204,7 @@
* [SameTreesCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SameTreesCheck.java)
* [SegmentTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java)
* [SplayTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SplayTree.java)
+ * [Treap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/Treap.java)
* [TreeRandomNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TreeRandomNode.java)
* [TrieImp](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java)
* [VerticalOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversal.java)
@@ -457,6 +459,7 @@
* [GenerateSubsets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java)
* scheduling
* [FCFSScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/FCFSScheduling.java)
+ * [MLFQScheduler](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java)
* [PreemptivePriorityScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java)
* [RRScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/RRScheduling.java)
* [SJFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java)
@@ -618,6 +621,7 @@
* ciphers
* a5
* [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java)
+ * [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java)
* [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java)
* [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java)
* [DESTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DESTest.java)
@@ -728,6 +732,7 @@
* [PreOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/PreOrderTraversalTest.java)
* [SameTreesCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SameTreesCheckTest.java)
* [SplayTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SplayTreeTest.java)
+ * [TreapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java)
* [TreeTestUtils](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreeTestUtils.java)
* [TrieImpTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java)
* [VerticalOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversalTest.java)
@@ -911,6 +916,7 @@
* [GenerateSubsetsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/Recursion/GenerateSubsetsTest.java)
* scheduling
* [FCFSSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/FCFSSchedulingTest.java)
+ * [MLFQSchedulerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java)
* [PreemptivePrioritySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java)
* [RRSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/RRSchedulingTest.java)
* [SJFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java)
diff --git a/src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java b/src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java
new file mode 100644
index 000000000000..75840a5cbdcf
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java
@@ -0,0 +1,148 @@
+package com.thealgorithms.scheduling;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * The Multi-Level Feedback Queue (MLFQ) Scheduler class.
+ * This class simulates scheduling using multiple queues, where processes move
+ * between queues depending on their CPU burst behavior.
+ */
+public class MLFQScheduler {
+ private List> queues; // Multi-level feedback queues
+ private int[] timeQuantum; // Time quantum for each queue level
+ private int currentTime; // Current time in the system
+
+ /**
+ * Constructor to initialize the MLFQ scheduler with the specified number of
+ * levels and their corresponding time quantums.
+ *
+ * @param levels Number of queues (priority levels)
+ * @param timeQuantums Time quantum for each queue level
+ */
+ public MLFQScheduler(int levels, int[] timeQuantums) {
+ queues = new ArrayList<>(levels);
+ for (int i = 0; i < levels; i++) {
+ queues.add(new LinkedList<>());
+ }
+ timeQuantum = timeQuantums;
+ currentTime = 0;
+ }
+
+ /**
+ * Adds a new process to the highest priority queue (queue 0).
+ *
+ * @param p The process to be added to the scheduler
+ */
+ public void addProcess(Process p) {
+ queues.get(0).add(p);
+ }
+
+ /**
+ * Executes the scheduling process by running the processes in all queues,
+ * promoting or demoting them based on their completion status and behavior.
+ * The process continues until all queues are empty.
+ */
+ public void run() {
+ while (!allQueuesEmpty()) {
+ for (int i = 0; i < queues.size(); i++) {
+ Queue queue = queues.get(i);
+ if (!queue.isEmpty()) {
+ Process p = queue.poll();
+ int quantum = timeQuantum[i];
+
+ // Execute the process for the minimum of the time quantum or the remaining time
+ int timeSlice = Math.min(quantum, p.remainingTime);
+ p.execute(timeSlice);
+ currentTime += timeSlice; // Update the system's current time
+
+ if (p.isFinished()) {
+ System.out.println("Process " + p.pid + " finished at time " + currentTime);
+ } else {
+ if (i < queues.size() - 1) {
+ p.priority++; // Demote the process to the next lower priority queue
+ queues.get(i + 1).add(p); // Add to the next queue level
+ } else {
+ queue.add(p); // Stay in the same queue if it's the last level
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function to check if all the queues are empty (i.e., no process is
+ * left to execute).
+ *
+ * @return true if all queues are empty, otherwise false
+ */
+ private boolean allQueuesEmpty() {
+ for (Queue queue : queues) {
+ if (!queue.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Retrieves the current time of the scheduler, which reflects the total time
+ * elapsed during the execution of all processes.
+ *
+ * @return The current time in the system
+ */
+ public int getCurrentTime() {
+ return currentTime;
+ }
+}
+
+/**
+ * Represents a process in the Multi-Level Feedback Queue (MLFQ) scheduling
+ * algorithm.
+ */
+class Process {
+ int pid;
+ int burstTime;
+ int remainingTime;
+ int arrivalTime;
+ int priority;
+
+ /**
+ * Constructor to initialize a new process.
+ *
+ * @param pid Process ID
+ * @param burstTime CPU Burst Time (time required for the process)
+ * @param arrivalTime Arrival time of the process
+ */
+ Process(int pid, int burstTime, int arrivalTime) {
+ this.pid = pid;
+ this.burstTime = burstTime;
+ this.remainingTime = burstTime;
+ this.arrivalTime = arrivalTime;
+ this.priority = 0;
+ }
+
+ /**
+ * Executes the process for a given time slice.
+ *
+ * @param timeSlice The amount of time the process is executed
+ */
+ public void execute(int timeSlice) {
+ remainingTime -= timeSlice;
+ if (remainingTime < 0) {
+ remainingTime = 0;
+ }
+ }
+
+ /**
+ * Checks if the process has finished execution.
+ *
+ * @return true if the process is finished, otherwise false
+ */
+ public boolean isFinished() {
+ return remainingTime == 0;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java b/src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java
new file mode 100644
index 000000000000..d7d27e9b32b6
--- /dev/null
+++ b/src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.scheduling;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class MLFQSchedulerTest {
+
+ @Test
+ void testMLFQScheduling() {
+ // Create MLFQ Scheduler with 3 levels and time quantum for each level
+ int[] timeQuantums = {4, 8, 12}; // Example of different quantum for each queue
+ MLFQScheduler scheduler = new MLFQScheduler(3, timeQuantums);
+
+ // Add processes to the scheduler
+ scheduler.addProcess(new Process(1, 10, 0)); // pid=1, burstTime=10, arrivalTime=0
+ scheduler.addProcess(new Process(2, 15, 0)); // pid=2, burstTime=15, arrivalTime=0
+ scheduler.addProcess(new Process(3, 25, 0)); // pid=3, burstTime=25, arrivalTime=0
+
+ // Run the scheduler
+ scheduler.run();
+
+ // Check current time after all processes are finished
+ assertEquals(50, scheduler.getCurrentTime());
+ }
+
+ @Test
+ void testProcessCompletionOrder() {
+ int[] timeQuantums = {3, 6, 9};
+ MLFQScheduler scheduler = new MLFQScheduler(3, timeQuantums);
+
+ Process p1 = new Process(1, 10, 0);
+ Process p2 = new Process(2, 5, 0);
+ Process p3 = new Process(3, 20, 0);
+
+ scheduler.addProcess(p1);
+ scheduler.addProcess(p2);
+ scheduler.addProcess(p3);
+
+ scheduler.run();
+
+ // After running, current time should match the total burst time for all
+ // processes
+ assertEquals(35, scheduler.getCurrentTime());
+ }
+}
From b190cb72def8d926e2f73d9f80cfd27c88ff3a3a Mon Sep 17 00:00:00 2001
From: Gopi Gorantala
Date: Sun, 6 Oct 2024 12:45:32 +0530
Subject: [PATCH 077/457] Add countsetbits problem with lookup table approach
(#5573)
---
.../bitmanipulation/CountSetBits.java | 28 +++++++++++++++++++
.../bitmanipulation/CountSetBitsTest.java | 9 ++++++
2 files changed, 37 insertions(+)
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
index eb0886e30292..242f35fc35f2 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java
@@ -48,4 +48,32 @@ public long countSetBits(long num) {
}
return cnt;
}
+
+ /**
+ * This approach takes O(1) running time to count the set bits, but requires a pre-processing.
+ *
+ * So, we divide our 32-bit input into 8-bit chunks, with four chunks. We have 8 bits in each chunk.
+ *
+ * Then the range is from 0-255 (0 to 2^7).
+ * So, we may need to count set bits from 0 to 255 in individual chunks.
+ *
+ * @param num takes a long number
+ * @return the count of set bits in the binary equivalent
+ */
+ public int lookupApproach(int num) {
+ int[] table = new int[256];
+ table[0] = 0;
+
+ for (int i = 1; i < 256; i++) {
+ table[i] = (i & 1) + table[i >> 1]; // i >> 1 equals to i/2
+ }
+
+ int res = 0;
+ for (int i = 0; i < 4; i++) {
+ res += table[num & 0xff];
+ num >>= 8;
+ }
+
+ return res;
+ }
}
diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java
index 412312109bec..61e0757f9c12 100644
--- a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java
+++ b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java
@@ -14,4 +14,13 @@ void testSetBits() {
assertEquals(5, csb.countSetBits(10000));
assertEquals(5, csb.countSetBits(31));
}
+
+ @Test
+ void testSetBitsLookupApproach() {
+ CountSetBits csb = new CountSetBits();
+ assertEquals(1L, csb.lookupApproach(16));
+ assertEquals(4, csb.lookupApproach(15));
+ assertEquals(5, csb.lookupApproach(10000));
+ assertEquals(5, csb.lookupApproach(31));
+ }
}
From afc06d56320f6a3d99af8270b75c11c760ff02a7 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sun, 6 Oct 2024 23:11:29 +0530
Subject: [PATCH 078/457] Enhance class & function documentation in
`LFUcache.java` (#5583)
* Enhance class & function documentation in `LFUcache.java`
* Fix
---------
Co-authored-by: Alex Klymenko
---
.../datastructures/caches/LFUCache.java | 60 +++++++++++++++----
1 file changed, 49 insertions(+), 11 deletions(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
index a5b83af14551..4e233224e367 100644
--- a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
+++ b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java
@@ -4,11 +4,27 @@
import java.util.Map;
/**
- * Java program for LFU Cache (https://en.wikipedia.org/wiki/Least_frequently_used)
+ * The {@code LFUCache} class implements a Least Frequently Used (LFU) cache.
+ * An LFU cache evicts the least frequently used item when the cache reaches its capacity.
+ * It keeps track of how many times each item is used and maintains a doubly linked list
+ * for efficient addition and removal of items based on their frequency of use.
+ *
+ * @param The type of keys maintained by this cache.
+ * @param The type of mapped values.
+ *
+ *
+ * Reference: LFU Cache - Wikipedia
+ *
+ *
* @author Akshay Dubey (https://github.com/itsAkshayDubey)
*/
public class LFUCache {
+ /**
+ * The {@code Node} class represents an element in the LFU cache.
+ * Each node contains a key, a value, and a frequency count.
+ * It also has pointers to the previous and next nodes in the doubly linked list.
+ */
private class Node {
private final K key;
private V value;
@@ -16,6 +32,13 @@ private class Node {
private Node previous;
private Node next;
+ /**
+ * Constructs a new {@code Node} with the specified key, value, and frequency.
+ *
+ * @param key The key associated with this node.
+ * @param value The value stored in this node.
+ * @param frequency The frequency of usage of this node.
+ */
Node(K key, V value, int frequency) {
this.key = key;
this.value = value;
@@ -29,10 +52,19 @@ private class Node {
private final int capacity;
private static final int DEFAULT_CAPACITY = 100;
+ /**
+ * Constructs an LFU cache with the default capacity.
+ */
public LFUCache() {
this(DEFAULT_CAPACITY);
}
+ /**
+ * Constructs an LFU cache with the specified capacity.
+ *
+ * @param capacity The maximum number of items that the cache can hold.
+ * @throws IllegalArgumentException if the specified capacity is less than or equal to zero.
+ */
public LFUCache(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("Capacity must be greater than zero.");
@@ -42,10 +74,12 @@ public LFUCache(int capacity) {
}
/**
- * Retrieves the value for the given key from the cache. Increases the frequency of the node.
+ * Retrieves the value associated with the given key from the cache.
+ * If the key exists, the node's frequency is increased and the node is repositioned
+ * in the linked list based on its updated frequency.
*
- * @param key The key to look up.
- * @return The value associated with the key, or null if the key is not present.
+ * @param key The key whose associated value is to be returned.
+ * @return The value associated with the key, or {@code null} if the key is not present in the cache.
*/
public V get(K key) {
Node node = cache.get(key);
@@ -59,10 +93,12 @@ public V get(K key) {
}
/**
- * Adds or updates a key-value pair in the cache. If the cache is full, the least frequently used item is evicted.
+ * Inserts or updates a key-value pair in the cache.
+ * If the key already exists, the value is updated and its frequency is incremented.
+ * If the cache is full, the least frequently used item is removed before inserting the new item.
*
- * @param key The key to insert or update.
- * @param value The value to insert or update.
+ * @param key The key associated with the value to be inserted or updated.
+ * @param value The value to be inserted or updated.
*/
public void put(K key, V value) {
if (cache.containsKey(key)) {
@@ -73,7 +109,7 @@ public void put(K key, V value) {
addNodeWithUpdatedFrequency(node);
} else {
if (cache.size() >= capacity) {
- cache.remove(this.head.key);
+ cache.remove(this.head.key); // Evict least frequently used item
removeNode(head);
}
Node node = new Node(key, value, 1);
@@ -84,8 +120,9 @@ public void put(K key, V value) {
/**
* Adds a node to the linked list in the correct position based on its frequency.
+ * The linked list is ordered by frequency, with the least frequently used node at the head.
*
- * @param node The node to add.
+ * @param node The node to be inserted into the list.
*/
private void addNodeWithUpdatedFrequency(Node node) {
if (tail != null && head != null) {
@@ -122,9 +159,10 @@ private void addNodeWithUpdatedFrequency(Node node) {
}
/**
- * Removes a node from the linked list.
+ * Removes a node from the doubly linked list.
+ * This method ensures that the pointers of neighboring nodes are properly updated.
*
- * @param node The node to remove.
+ * @param node The node to be removed from the list.
*/
private void removeNode(Node node) {
if (node.previous != null) {
From ee6cd648bc4ea9e929548b8c93d841baf173925b Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Sun, 6 Oct 2024 23:42:57 +0530
Subject: [PATCH 079/457] Strengthen class & function documentation in
`CompositeLFSR.java` (#5596)
Co-authored-by: Alex Klymenko
---
.../ciphers/a5/CompositeLFSR.java | 36 +++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java b/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java
index f96946c39490..029a93848c28 100644
--- a/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java
+++ b/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java
@@ -5,13 +5,33 @@
import java.util.Map;
import java.util.TreeMap;
+/**
+ * The CompositeLFSR class represents a composite implementation of
+ * Linear Feedback Shift Registers (LFSRs) for cryptographic purposes.
+ *
+ *
+ * This abstract class manages a collection of LFSR instances and
+ * provides a mechanism for irregular clocking based on the
+ * majority bit among the registers. It implements the BaseLFSR
+ * interface, requiring subclasses to define specific LFSR behaviors.
+ *
+ */
public abstract class CompositeLFSR implements BaseLFSR {
protected final List registers = new ArrayList<>();
/**
- * Implements irregular clocking using the clock bit for each register
- * @return the registers discarded bit xored value
+ * Performs a clocking operation on the composite LFSR.
+ *
+ *
+ * This method determines the majority bit across all registers and
+ * clocks each register based on its clock bit. If a register's
+ * clock bit matches the majority bit, it is clocked (shifted).
+ * The method also computes and returns the XOR of the last bits
+ * of all registers.
+ *
+ *
+ * @return the XOR value of the last bits of all registers.
*/
@Override
public boolean clock() {
@@ -26,6 +46,18 @@ public boolean clock() {
return result;
}
+ /**
+ * Calculates the majority bit among all registers.
+ *
+ *
+ * This private method counts the number of true and false clock bits
+ * across all LFSR registers. It returns true if the count of true
+ * bits is greater than or equal to the count of false bits; otherwise,
+ * it returns false.
+ *
+ *
+ * @return true if the majority clock bits are true; false otherwise.
+ */
private boolean getMajorityBit() {
Map bitCount = new TreeMap<>();
bitCount.put(Boolean.FALSE, 0);
From 2001a097e2ab9fd2f63d444ace41cf3c59e2e251 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 16:55:08 +0530
Subject: [PATCH 080/457] Add `HighestResponseRatioNextScheduling.java` new
algorithm with tests (#5607)
* Add `HighestResponseRatioNextScheduling.java` new algorithm with tests
* Update directory
* Improve class documentation
* Update directory
* Fix
* Fix
* Fix
* Add suggested changes
* Fix clang errors
---------
Co-authored-by: Hardvan
Co-authored-by: Alex Klymenko
---
DIRECTORY.md | 2 +
.../HighestResponseRatioNextScheduling.java | 158 ++++++++++++++++++
...ighestResponseRatioNextSchedulingTest.java | 112 +++++++++++++
3 files changed, 272 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java
create mode 100644 src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index d93dde3dffb6..7e9f5357a825 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -459,6 +459,7 @@
* [GenerateSubsets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/Recursion/GenerateSubsets.java)
* scheduling
* [FCFSScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/FCFSScheduling.java)
+ * [HighestResponseRatioNextScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java)
* [MLFQScheduler](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java)
* [PreemptivePriorityScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java)
* [RRScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/RRScheduling.java)
@@ -916,6 +917,7 @@
* [GenerateSubsetsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/Recursion/GenerateSubsetsTest.java)
* scheduling
* [FCFSSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/FCFSSchedulingTest.java)
+ * [HighestResponseRatioNextSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java)
* [MLFQSchedulerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java)
* [PreemptivePrioritySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java)
* [RRSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/RRSchedulingTest.java)
diff --git a/src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java b/src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java
new file mode 100644
index 000000000000..8ed689698557
--- /dev/null
+++ b/src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java
@@ -0,0 +1,158 @@
+package com.thealgorithms.scheduling;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * The {@code HighestResponseRatioNextScheduling} class implements the
+ * Highest Response Ratio Next (HRRN) scheduling algorithm.
+ * HRRN is a non-preemptive scheduling algorithm that selects the process with
+ * the highest response ratio for execution.
+ * The response ratio is calculated as:
+ *
+ *
+ * Response Ratio = (waiting time + burst time) / burst time
+ *
+ *
+ * HRRN is designed to reduce the average waiting time and improve overall
+ * system performance by balancing between short and long processes,
+ * minimizing process starvation.
+ */
+public final class HighestResponseRatioNextScheduling {
+
+ private static final int PROCESS_NOT_FOUND = -1;
+ private static final double INITIAL_MAX_RESPONSE_RATIO = -1.0;
+
+ private HighestResponseRatioNextScheduling() {
+ }
+
+ /**
+ * Represents a process in the scheduling algorithm.
+ */
+ private static class Process {
+ String name;
+ int arrivalTime;
+ int burstTime;
+ int turnAroundTime;
+ boolean finished;
+
+ Process(String name, int arrivalTime, int burstTime) {
+ this.name = name;
+ this.arrivalTime = arrivalTime;
+ this.burstTime = burstTime;
+ this.turnAroundTime = 0;
+ this.finished = false;
+ }
+
+ /**
+ * Calculates the response ratio for this process.
+ *
+ * @param currentTime The current time in the scheduling process.
+ * @return The response ratio for this process.
+ */
+ double calculateResponseRatio(int currentTime) {
+ return (double) (burstTime + currentTime - arrivalTime) / burstTime;
+ }
+ }
+
+ /**
+ * Calculates the Turn Around Time (TAT) for each process.
+ *
+ * Turn Around Time is calculated as the total time a process spends
+ * in the system from arrival to completion. It is the sum of the burst time
+ * and the waiting time.
+ *
+ * @param processNames Array of process names.
+ * @param arrivalTimes Array of arrival times corresponding to each process.
+ * @param burstTimes Array of burst times for each process.
+ * @param noOfProcesses The number of processes.
+ * @return An array of Turn Around Times for each process.
+ */
+ public static int[] calculateTurnAroundTime(final String[] processNames, final int[] arrivalTimes, final int[] burstTimes, final int noOfProcesses) {
+ int currentTime = 0;
+ int[] turnAroundTime = new int[noOfProcesses];
+ Process[] processes = new Process[noOfProcesses];
+
+ for (int i = 0; i < noOfProcesses; i++) {
+ processes[i] = new Process(processNames[i], arrivalTimes[i], burstTimes[i]);
+ }
+
+ Arrays.sort(processes, Comparator.comparingInt(p -> p.arrivalTime));
+
+ int finishedProcessCount = 0;
+ while (finishedProcessCount < noOfProcesses) {
+ int nextProcessIndex = findNextProcess(processes, currentTime);
+ if (nextProcessIndex == PROCESS_NOT_FOUND) {
+ currentTime++;
+ continue;
+ }
+
+ Process currentProcess = processes[nextProcessIndex];
+ currentTime = Math.max(currentTime, currentProcess.arrivalTime);
+ currentProcess.turnAroundTime = currentTime + currentProcess.burstTime - currentProcess.arrivalTime;
+ currentTime += currentProcess.burstTime;
+ currentProcess.finished = true;
+ finishedProcessCount++;
+ }
+
+ for (int i = 0; i < noOfProcesses; i++) {
+ turnAroundTime[i] = processes[i].turnAroundTime;
+ }
+
+ return turnAroundTime;
+ }
+
+ /**
+ * Calculates the Waiting Time (WT) for each process.
+ *
+ * @param turnAroundTime The Turn Around Times for each process.
+ * @param burstTimes The burst times for each process.
+ * @return An array of Waiting Times for each process.
+ */
+ public static int[] calculateWaitingTime(int[] turnAroundTime, int[] burstTimes) {
+ int[] waitingTime = new int[turnAroundTime.length];
+ for (int i = 0; i < turnAroundTime.length; i++) {
+ waitingTime[i] = turnAroundTime[i] - burstTimes[i];
+ }
+ return waitingTime;
+ }
+
+ /**
+ * Finds the next process to be scheduled based on arrival times and the current time.
+ *
+ * @param processes Array of Process objects.
+ * @param currentTime The current time in the scheduling process.
+ * @return The index of the next process to be scheduled, or PROCESS_NOT_FOUND if no process is ready.
+ */
+ private static int findNextProcess(Process[] processes, int currentTime) {
+ return findHighestResponseRatio(processes, currentTime);
+ }
+
+ /**
+ * Finds the process with the highest response ratio.
+ *
+ * The response ratio is calculated as:
+ * (waiting time + burst time) / burst time
+ * where waiting time = current time - arrival time
+ *
+ * @param processes Array of Process objects.
+ * @param currentTime The current time in the scheduling process.
+ * @return The index of the process with the highest response ratio, or PROCESS_NOT_FOUND if no process is ready.
+ */
+ private static int findHighestResponseRatio(Process[] processes, int currentTime) {
+ double maxResponseRatio = INITIAL_MAX_RESPONSE_RATIO;
+ int maxIndex = PROCESS_NOT_FOUND;
+
+ for (int i = 0; i < processes.length; i++) {
+ Process process = processes[i];
+ if (!process.finished && process.arrivalTime <= currentTime) {
+ double responseRatio = process.calculateResponseRatio(currentTime);
+ if (responseRatio > maxResponseRatio) {
+ maxResponseRatio = responseRatio;
+ maxIndex = i;
+ }
+ }
+ }
+ return maxIndex;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java
new file mode 100644
index 000000000000..d225d76b82c9
--- /dev/null
+++ b/src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java
@@ -0,0 +1,112 @@
+package com.thealgorithms.scheduling;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class HighestResponseRatioNextSchedulingTest {
+
+ @Test
+ public void testCalculateTurnAroundTime() {
+ String[] processNames = {"A", "B", "C"};
+ int[] arrivalTimes = {0, 2, 4};
+ int[] burstTimes = {3, 1, 2};
+ int noOfProcesses = 3;
+
+ int[] expectedTurnAroundTimes = {3, 2, 2};
+ int[] actualTurnAroundTimes = HighestResponseRatioNextScheduling.calculateTurnAroundTime(processNames, arrivalTimes, burstTimes, noOfProcesses);
+
+ assertArrayEquals(expectedTurnAroundTimes, actualTurnAroundTimes, "Turn Around Times do not match");
+ }
+
+ @Test
+ public void testCalculateWaitingTime() {
+ int[] turnAroundTimes = {3, 1, 5};
+ int[] burstTimes = {3, 1, 2};
+
+ int[] expectedWaitingTimes = {0, 0, 3};
+ int[] actualWaitingTimes = HighestResponseRatioNextScheduling.calculateWaitingTime(turnAroundTimes, burstTimes);
+
+ assertArrayEquals(expectedWaitingTimes, actualWaitingTimes, "Waiting Times do not match");
+ }
+
+ @Test
+ public void testCompleteSchedulingScenario() {
+ String[] processNames = {"A", "B", "C"};
+ int[] arrivalTimes = {0, 1, 2};
+ int[] burstTimes = {5, 2, 1};
+
+ int[] expectedTurnAroundTimes = {5, 7, 4};
+ int[] turnAroundTimes = HighestResponseRatioNextScheduling.calculateTurnAroundTime(processNames, arrivalTimes, burstTimes, processNames.length);
+ assertArrayEquals(expectedTurnAroundTimes, turnAroundTimes, "Turn Around Times do not match");
+
+ int[] expectedWaitingTimes = {0, 5, 3};
+ int[] waitingTimes = HighestResponseRatioNextScheduling.calculateWaitingTime(turnAroundTimes, burstTimes);
+ assertArrayEquals(expectedWaitingTimes, waitingTimes, "Waiting Times do not match");
+ }
+
+ @Test
+ public void testZeroProcesses() {
+ String[] processNames = {};
+ int[] arrivalTimes = {};
+ int[] burstTimes = {};
+ int noOfProcesses = 0;
+
+ int[] expectedTurnAroundTimes = {};
+ int[] actualTurnAroundTimes = HighestResponseRatioNextScheduling.calculateTurnAroundTime(processNames, arrivalTimes, burstTimes, noOfProcesses);
+
+ assertArrayEquals(expectedTurnAroundTimes, actualTurnAroundTimes, "Turn Around Times for zero processes should be an empty array");
+ }
+
+ @Test
+ public void testAllProcessesArriveAtSameTime() {
+ String[] processNames = {"A", "B", "C", "D"};
+ int[] arrivalTimes = {0, 0, 0, 0};
+ int[] burstTimes = {4, 3, 1, 2};
+ int noOfProcesses = 4;
+
+ int[] expectedTurnAroundTimes = {4, 10, 5, 7};
+ int[] actualTurnAroundTimes = HighestResponseRatioNextScheduling.calculateTurnAroundTime(processNames, arrivalTimes, burstTimes, noOfProcesses);
+
+ assertArrayEquals(expectedTurnAroundTimes, actualTurnAroundTimes, "Turn Around Times for processes arriving at the same time do not match");
+ }
+
+ @Test
+ public void testProcessesWithZeroBurstTime() {
+ String[] processNames = {"A", "B", "C"};
+ int[] arrivalTimes = {0, 1, 2};
+ int[] burstTimes = {3, 0, 2};
+ int noOfProcesses = 3;
+
+ int[] expectedTurnAroundTimes = {3, 2, 3};
+ int[] actualTurnAroundTimes = HighestResponseRatioNextScheduling.calculateTurnAroundTime(processNames, arrivalTimes, burstTimes, noOfProcesses);
+
+ assertArrayEquals(expectedTurnAroundTimes, actualTurnAroundTimes, "Turn Around Times for processes with zero burst time do not match");
+ }
+
+ @Test
+ public void testProcessesWithLargeGapsBetweenArrivals() {
+ String[] processNames = {"A", "B", "C"};
+ int[] arrivalTimes = {0, 100, 200};
+ int[] burstTimes = {10, 10, 10};
+ int noOfProcesses = 3;
+
+ int[] expectedTurnAroundTimes = {10, 10, 10};
+ int[] actualTurnAroundTimes = HighestResponseRatioNextScheduling.calculateTurnAroundTime(processNames, arrivalTimes, burstTimes, noOfProcesses);
+
+ assertArrayEquals(expectedTurnAroundTimes, actualTurnAroundTimes, "Turn Around Times for processes with large gaps between arrivals do not match");
+ }
+
+ @Test
+ public void testProcessesWithVeryLargeBurstTimes() {
+ String[] processNames = {"A", "B"};
+ int[] arrivalTimes = {0, 1};
+ int[] burstTimes = {Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 2};
+ int noOfProcesses = 2;
+
+ int[] expectedTurnAroundTimes = {Integer.MAX_VALUE / 2, Integer.MAX_VALUE - 2};
+ int[] actualTurnAroundTimes = HighestResponseRatioNextScheduling.calculateTurnAroundTime(processNames, arrivalTimes, burstTimes, noOfProcesses);
+
+ assertArrayEquals(expectedTurnAroundTimes, actualTurnAroundTimes, "Turn Around Times for processes with very large burst times do not match");
+ }
+}
From 387707ffe530bd3b296443700ac08455256b618a Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:01:13 +0530
Subject: [PATCH 081/457] Refactor BipartiteGraphDFS.java, add Junit tests
(#5606)
* Refactor `BipartiteGraphDFS.java`, add Junit tests
* Update directory
* Fix
* Add suggested changes
* Update BipartiteGraphDFS.java
---------
Co-authored-by: Hardvan
Co-authored-by: Alex Klymenko
---
DIRECTORY.md | 1 +
.../graphs/BipartiteGraphDFS.java | 78 ++++++++++---------
.../graphs/BipartiteGraphDFSTest.java | 73 +++++++++++++++++
3 files changed, 114 insertions(+), 38 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 7e9f5357a825..d3136f95c062 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -676,6 +676,7 @@
* dynamicarray
* [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java)
* graphs
+ * [BipartiteGraphDFSTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java)
* [BoruvkaAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java)
* [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java)
* [EdmondsBlossomAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java)
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java b/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java
index e8d2b8fd0a04..15ae5225533c 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java
@@ -1,23 +1,49 @@
package com.thealgorithms.datastructures.graphs;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
/**
- * Given an adjacency list of a graph adj of V no. of vertices having 0 based
- * index. Check whether the graph is bipartite or not.
+ * This class provides a method to check if a given undirected graph is bipartite using Depth-First Search (DFS).
+ * A bipartite graph is a graph whose vertices can be divided into two disjoint sets such that no two vertices
+ * within the same set are adjacent. In other words, all edges must go between the two sets.
*
- * Input : {{0, 1, 0, 1}, {1, 0, 1, 0}, {0, 1, 0, 1}, {1, 0, 1, 0}}
+ * The implementation leverages DFS to attempt to color the graph using two colors. If we can color the graph such
+ * that no two adjacent vertices have the same color, the graph is bipartite.
*
- * Output : YES
+ * Example:
+ * Input (Adjacency Matrix):
+ * {{0, 1, 0, 1},
+ * {1, 0, 1, 0},
+ * {0, 1, 0, 1},
+ * {1, 0, 1, 0}}
+ *
+ * Output: YES (This graph is bipartite)
+ *
+ * Input (Adjacency Matrix):
+ * {{0, 1, 1, 0},
+ * {1, 0, 1, 0},
+ * {1, 1, 0, 1},
+ * {0, 0, 1, 0}}
+ *
+ * Output: NO (This graph is not bipartite)
*/
public final class BipartiteGraphDFS {
private BipartiteGraphDFS() {
}
+ /**
+ * Helper method to perform DFS and check if the graph is bipartite.
+ *
+ * During DFS traversal, this method attempts to color each vertex in such a way
+ * that no two adjacent vertices share the same color.
+ *
+ * @param v Number of vertices in the graph
+ * @param adj Adjacency list of the graph where each index i contains a list of adjacent vertices
+ * @param color Array to store the color assigned to each vertex (-1 indicates uncolored)
+ * @param node Current vertex being processed
+ * @return True if the graph (or component of the graph) is bipartite, otherwise false
+ */
private static boolean bipartite(int v, ArrayList> adj, int[] color, int node) {
if (color[node] == -1) {
color[node] = 1;
@@ -35,11 +61,16 @@ private static boolean bipartite(int v, ArrayList> adj, int[]
return true;
}
+ /**
+ * Method to check if the graph is bipartite.
+ *
+ * @param v Number of vertices in the graph
+ * @param adj Adjacency list of the graph
+ * @return True if the graph is bipartite, otherwise false
+ */
public static boolean isBipartite(int v, ArrayList> adj) {
- // Code here
int[] color = new int[v + 1];
Arrays.fill(color, -1);
-
for (int i = 0; i < v; i++) {
if (color[i] == -1) {
if (!bipartite(v, adj, color, i)) {
@@ -49,33 +80,4 @@ public static boolean isBipartite(int v, ArrayList> adj) {
}
return true;
}
-
- public static void main(String[] args) throws IOException {
- BufferedReader read = new BufferedReader(new InputStreamReader(System.in));
- int t = Integer.parseInt(read.readLine().trim());
- while (t-- > 0) {
- String[] str1 = read.readLine().trim().split(" ");
- int numVertices = Integer.parseInt(str1[0]);
- int numEdges = Integer.parseInt(str1[1]);
-
- ArrayList> adj = new ArrayList<>();
- for (int i = 0; i < numVertices; i++) {
- adj.add(new ArrayList<>());
- }
- for (int i = 0; i < numEdges; i++) {
- String[] str2 = read.readLine().trim().split(" ");
- int vertexU = Integer.parseInt(str2[0]);
- int vertexV = Integer.parseInt(str2[1]);
- adj.get(vertexU).add(vertexV);
- adj.get(vertexV).add(vertexU);
- }
-
- boolean ans = isBipartite(numVertices, adj);
- if (ans) {
- System.out.println("YES");
- } else {
- System.out.println("NO");
- }
- }
- }
}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java
new file mode 100644
index 000000000000..75fa6adc3014
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java
@@ -0,0 +1,73 @@
+package com.thealgorithms.datastructures.graphs;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import org.junit.jupiter.api.Test;
+
+public class BipartiteGraphDFSTest {
+
+ // Helper method to create an adjacency list from edges
+ private ArrayList> createAdjacencyList(int numVertices, int[][] edges) {
+ ArrayList> adj = new ArrayList<>();
+ for (int i = 0; i < numVertices; i++) {
+ adj.add(new ArrayList<>());
+ }
+ for (int[] edge : edges) {
+ int vertexU = edge[0];
+ int vertexV = edge[1];
+ adj.get(vertexU).add(vertexV);
+ adj.get(vertexV).add(vertexU);
+ }
+ return adj;
+ }
+
+ @Test
+ public void testBipartiteGraphEvenCycle() {
+ int numVertices = 4;
+ int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 0}}; // Even cycle
+ ArrayList> adj = createAdjacencyList(numVertices, edges);
+ assertTrue(BipartiteGraphDFS.isBipartite(numVertices, adj), "Graph should be bipartite (even cycle)");
+ }
+
+ @Test
+ public void testBipartiteGraphOddCycle() {
+ int numVertices = 5;
+ int[][] edges = {{0, 1}, {1, 2}, {2, 0}, {1, 3}, {3, 4}}; // Odd cycle
+ ArrayList> adj = createAdjacencyList(numVertices, edges);
+ assertFalse(BipartiteGraphDFS.isBipartite(numVertices, adj), "Graph should not be bipartite (odd cycle)");
+ }
+
+ @Test
+ public void testBipartiteGraphDisconnected() {
+ int numVertices = 6;
+ int[][] edges = {{0, 1}, {2, 3}, {4, 5}}; // Disconnected bipartite graphs
+ ArrayList> adj = createAdjacencyList(numVertices, edges);
+ assertTrue(BipartiteGraphDFS.isBipartite(numVertices, adj), "Graph should be bipartite (disconnected)");
+ }
+
+ @Test
+ public void testBipartiteGraphSingleVertex() {
+ int numVertices = 1;
+ int[][] edges = {}; // Single vertex, no edges
+ ArrayList> adj = createAdjacencyList(numVertices, edges);
+ assertTrue(BipartiteGraphDFS.isBipartite(numVertices, adj), "Graph should be bipartite (single vertex)");
+ }
+
+ @Test
+ public void testBipartiteGraphCompleteBipartite() {
+ int numVertices = 4;
+ int[][] edges = {{0, 2}, {0, 3}, {1, 2}, {1, 3}}; // K2,2 (Complete bipartite graph)
+ ArrayList> adj = createAdjacencyList(numVertices, edges);
+ assertTrue(BipartiteGraphDFS.isBipartite(numVertices, adj), "Graph should be bipartite (complete bipartite)");
+ }
+
+ @Test
+ public void testBipartiteGraphNonBipartite() {
+ int numVertices = 3;
+ int[][] edges = {{0, 1}, {1, 2}, {2, 0}}; // Triangle (odd cycle)
+ ArrayList> adj = createAdjacencyList(numVertices, edges);
+ assertFalse(BipartiteGraphDFS.isBipartite(numVertices, adj), "Graph should not be bipartite (triangle)");
+ }
+}
From 2cdd97cf5f076cccc640a2d75504636e8816f8f8 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:32:57 +0530
Subject: [PATCH 082/457] Improve class & function documentation in
`HighestSetBit.java` (#5577)
---
.../bitmanipulation/HighestSetBit.java | 32 ++++++++++++++++---
1 file changed, 27 insertions(+), 5 deletions(-)
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java
index 6b53b1aa182b..2398b8214371 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java
@@ -1,17 +1,39 @@
package com.thealgorithms.bitmanipulation;
+
import java.util.Optional;
/**
* Find Highest Set Bit
- * This class provides a function calculating the position (or index)
- * of the most significant bit being set to 1 in a given integer.
- * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
+ *
+ * This class provides a utility method to calculate the position of the highest
+ * (most significant) bit that is set to 1 in a given non-negative integer.
+ * It is often used in bit manipulation tasks to find the left-most set bit in binary
+ * representation of a number.
+ *
+ * Example:
+ * - For input 18 (binary 10010), the highest set bit is at position 4 (zero-based index).
+ *
+ * @author Bama Charan Chhandogi
+ * @version 1.0
+ * @since 2021-06-23
*/
-
public final class HighestSetBit {
+
private HighestSetBit() {
}
+ /**
+ * Finds the highest (most significant) set bit in the given integer.
+ * The method returns the position (index) of the highest set bit as an {@link Optional}.
+ *
+ * - If the number is 0, no bits are set, and the method returns {@link Optional#empty()}.
+ * - If the number is negative, the method throws {@link IllegalArgumentException}.
+ *
+ * @param num The input integer for which the highest set bit is to be found. It must be non-negative.
+ * @return An {@link Optional} containing the index of the highest set bit (zero-based).
+ * Returns {@link Optional#empty()} if the number is 0.
+ * @throws IllegalArgumentException if the input number is negative.
+ */
public static Optional findHighestSetBit(int num) {
if (num < 0) {
throw new IllegalArgumentException("Input cannot be negative");
@@ -27,6 +49,6 @@ public static Optional findHighestSetBit(int num) {
position++;
}
- return Optional.of(position - 1);
+ return Optional.of(position - 1); // Subtract 1 to convert to zero-based index
}
}
From 9ce9443fa243b7a9c3ba5800f547ee87001ed295 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:36:08 +0530
Subject: [PATCH 083/457] Enhance class & function documentation in
`WordSearch.java` (#5578)
---
.../backtracking/WordSearch.java | 92 ++++++++++++-------
1 file changed, 61 insertions(+), 31 deletions(-)
diff --git a/src/main/java/com/thealgorithms/backtracking/WordSearch.java b/src/main/java/com/thealgorithms/backtracking/WordSearch.java
index f3a5b0433727..174ca90ccaab 100644
--- a/src/main/java/com/thealgorithms/backtracking/WordSearch.java
+++ b/src/main/java/com/thealgorithms/backtracking/WordSearch.java
@@ -1,35 +1,39 @@
package com.thealgorithms.backtracking;
-/*
-Word Search Problem (https://en.wikipedia.org/wiki/Word_search)
-
-Given an m x n grid of characters board and a string word, return true if word exists in the grid.
-
-The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are
-those horizontally or vertically neighboring. The same letter cell may not be used more than once.
-
-For example,
-Given board =
-
-[
- ['A','B','C','E'],
- ['S','F','C','S'],
- ['A','D','E','E']
-]
-word = "ABCCED", -> returns true,
-word = "SEE", -> returns true,
-word = "ABCB", -> returns false.
-*/
-
-/*
- Solution
- Depth First Search in matrix (as multiple sources possible) with backtracking
- like finding cycle in a directed graph. Maintain a record of path
-
- Tx = O(m * n * 3^L): for each cell, we look at 3 options (not 4 as that one will be visited), we
- do it L times Sx = O(L) : stack size is max L
-*/
-
+/**
+ * Word Search Problem
+ *
+ * This class solves the word search problem where given an m x n grid of characters (board)
+ * and a target word, the task is to check if the word exists in the grid.
+ * The word can be constructed from sequentially adjacent cells (horizontally or vertically),
+ * and the same cell may not be used more than once in constructing the word.
+ *
+ * Example:
+ * - For board =
+ * [
+ * ['A','B','C','E'],
+ * ['S','F','C','S'],
+ * ['A','D','E','E']
+ * ]
+ * and word = "ABCCED", -> returns true
+ * and word = "SEE", -> returns true
+ * and word = "ABCB", -> returns false
+ *
+ * Solution:
+ * - Depth First Search (DFS) with backtracking is used to explore possible paths from any cell
+ * matching the first letter of the word. DFS ensures that we search all valid paths, while
+ * backtracking helps in reverting decisions when a path fails to lead to a solution.
+ *
+ * Time Complexity: O(m * n * 3^L)
+ * - m = number of rows in the board
+ * - n = number of columns in the board
+ * - L = length of the word
+ * - For each cell, we look at 3 possible directions (since we exclude the previously visited direction),
+ * and we do this for L letters.
+ *
+ * Space Complexity: O(L)
+ * - Stack space for the recursive DFS function, where L is the maximum depth of recursion (length of the word).
+ */
public class WordSearch {
private final int[] dx = {0, 0, 1, -1};
private final int[] dy = {1, -1, 0, 0};
@@ -37,15 +41,32 @@ public class WordSearch {
private char[][] board;
private String word;
+ /**
+ * Checks if the given (x, y) coordinates are valid positions in the board.
+ *
+ * @param x The row index.
+ * @param y The column index.
+ * @return True if the coordinates are within the bounds of the board; false otherwise.
+ */
private boolean isValid(int x, int y) {
return x >= 0 && x < board.length && y >= 0 && y < board[0].length;
}
+ /**
+ * Performs Depth First Search (DFS) from the cell (x, y)
+ * to search for the next character in the word.
+ *
+ * @param x The current row index.
+ * @param y The current column index.
+ * @param nextIdx The index of the next character in the word to be matched.
+ * @return True if a valid path is found to match the remaining characters of the word; false otherwise.
+ */
private boolean doDFS(int x, int y, int nextIdx) {
visited[x][y] = true;
if (nextIdx == word.length()) {
return true;
}
+
for (int i = 0; i < 4; ++i) {
int xi = x + dx[i];
int yi = y + dy[i];
@@ -56,10 +77,19 @@ private boolean doDFS(int x, int y, int nextIdx) {
}
}
}
- visited[x][y] = false;
+
+ visited[x][y] = false; // Backtrack
return false;
}
+ /**
+ * Main function to check if the word exists in the board. It initiates DFS from any
+ * cell that matches the first character of the word.
+ *
+ * @param board The 2D grid of characters (the board).
+ * @param word The target word to search for in the board.
+ * @return True if the word exists in the board; false otherwise.
+ */
public boolean exist(char[][] board, String word) {
this.board = board;
this.word = word;
From dea806ea53430ac7bdc6d466f5a95e8c425d4c54 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:39:58 +0530
Subject: [PATCH 084/457] Add tests for `ClosestPair.java` (#5555)
---
DIRECTORY.md | 1 +
.../divideandconquer/ClosestPair.java | 2 +-
.../divideandconquer/ClosestPairTest.java | 75 +++++++++++++++++++
3 files changed, 77 insertions(+), 1 deletion(-)
create mode 100644 src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index d3136f95c062..6b44fa336a3a 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -741,6 +741,7 @@
* [ZigzagTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/ZigzagTraversalTest.java)
* divideandconquer
* [BinaryExponentiationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/BinaryExponentiationTest.java)
+ * [ClosestPairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java)
* [SkylineAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/SkylineAlgorithmTest.java)
* [StrassenMatrixMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplicationTest.java)
* dynamicprogramming
diff --git a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java
index aa453539ac94..cd26f9213651 100644
--- a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java
+++ b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java
@@ -13,7 +13,7 @@ public final class ClosestPair {
/**
* Input data, maximum 10000.
*/
- private Location[] array;
+ Location[] array;
/**
* Minimum point coordinate.
*/
diff --git a/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java
new file mode 100644
index 000000000000..38784228d68e
--- /dev/null
+++ b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java
@@ -0,0 +1,75 @@
+package com.thealgorithms.divideandconquer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+public class ClosestPairTest {
+
+ @Test
+ public void testBuildLocation() {
+ ClosestPair cp = new ClosestPair(1);
+ ClosestPair.Location point = cp.buildLocation(3.0, 4.0);
+ assertNotNull(point);
+ assertEquals(3.0, point.x);
+ assertEquals(4.0, point.y);
+ }
+
+ @Test
+ public void testCreateLocation() {
+ ClosestPair cp = new ClosestPair(5);
+ ClosestPair.Location[] locations = cp.createLocation(5);
+ assertNotNull(locations);
+ assertEquals(5, locations.length);
+ }
+
+ @Test
+ public void testXPartition() {
+ ClosestPair cp = new ClosestPair(5);
+ ClosestPair.Location[] points = new ClosestPair.Location[5];
+ points[0] = cp.buildLocation(2.0, 3.0);
+ points[1] = cp.buildLocation(5.0, 1.0);
+ points[2] = cp.buildLocation(1.0, 6.0);
+ points[3] = cp.buildLocation(4.0, 7.0);
+ points[4] = cp.buildLocation(3.0, 2.0);
+
+ int pivotIndex = cp.xPartition(points, 0, 4);
+ assertEquals(2, pivotIndex);
+ assertEquals(2.0, points[0].x);
+ assertEquals(1.0, points[1].x);
+ assertEquals(3.0, points[2].x);
+ assertEquals(4.0, points[3].x);
+ assertEquals(5.0, points[4].x);
+ }
+
+ @Test
+ public void testYPartition() {
+ ClosestPair cp = new ClosestPair(5);
+ ClosestPair.Location[] points = new ClosestPair.Location[5];
+ points[0] = cp.buildLocation(2.0, 3.0);
+ points[1] = cp.buildLocation(5.0, 1.0);
+ points[2] = cp.buildLocation(1.0, 6.0);
+ points[3] = cp.buildLocation(4.0, 7.0);
+ points[4] = cp.buildLocation(3.0, 2.0);
+
+ int pivotIndex = cp.yPartition(points, 0, 4);
+ assertEquals(1, pivotIndex);
+ assertEquals(2.0, points[1].y);
+ assertEquals(3.0, points[4].y);
+ assertEquals(1.0, points[0].y);
+ assertEquals(6.0, points[2].y);
+ assertEquals(7.0, points[3].y);
+ }
+
+ @Test
+ public void testBruteForce() {
+ ClosestPair cp = new ClosestPair(2);
+ ClosestPair.Location loc1 = cp.buildLocation(1.0, 2.0);
+ ClosestPair.Location loc2 = cp.buildLocation(4.0, 6.0);
+
+ ClosestPair.Location[] locations = new ClosestPair.Location[] {loc1, loc2};
+ double result = cp.bruteForce(locations);
+ assertEquals(5.0, result, 0.01);
+ }
+}
From 7f57fc92b45e6a747614a48d4675e91d538d88a9 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:44:26 +0530
Subject: [PATCH 085/457] Improve comments, function & class documentation in
`IndexOfRightMostSetBit.java` (#5579)
---
.../IndexOfRightMostSetBit.java | 22 +++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java
index b825916a8674..1b8962344ea7 100644
--- a/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java
+++ b/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java
@@ -1,13 +1,27 @@
package com.thealgorithms.bitmanipulation;
/**
- * Find The Index Of Right Most SetBit
- * @author Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
+ * Utility class for bit manipulation operations.
+ * This class provides methods to work with bitwise operations.
+ * Specifically, it includes a method to find the index of the rightmost set bit
+ * in an integer.
+ * This class is not meant to be instantiated.
+ *
+ * Author: Bama Charan Chhandogi (https://github.com/BamaCharanChhandogi)
*/
-
public final class IndexOfRightMostSetBit {
+
private IndexOfRightMostSetBit() {
}
+
+ /**
+ * Finds the index of the rightmost set bit in the given integer.
+ * The index is zero-based, meaning the rightmost bit has an index of 0.
+ *
+ * @param n the integer to check for the rightmost set bit
+ * @return the index of the rightmost set bit; -1 if there are no set bits
+ * (i.e., the input integer is 0)
+ */
public static int indexOfRightMostSetBit(int n) {
if (n == 0) {
return -1; // No set bits
@@ -16,7 +30,7 @@ public static int indexOfRightMostSetBit(int n) {
// Handle negative numbers by finding the two's complement
if (n < 0) {
n = -n;
- n = n & (~n + 1); // Get the rightmost set bit in positive form
+ n = n & (~n + 1); // Isolate the rightmost set bit
}
int index = 0;
From 357fc6a2715eea9ef04822b967effee61fa91eb7 Mon Sep 17 00:00:00 2001
From: Prayas Kumar <71717433+prayas7102@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:48:25 +0530
Subject: [PATCH 086/457] Add LowestSetBit (#5567)
---
.../bitmanipulation/LowestSetBit.java | 34 ++++++++
.../bitmanipulation/LowestSetBitTest.java | 86 +++++++++++++++++++
2 files changed, 120 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java
create mode 100644 src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java
diff --git a/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java b/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java
new file mode 100644
index 000000000000..127b6fa2c0b1
--- /dev/null
+++ b/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java
@@ -0,0 +1,34 @@
+package com.thealgorithms.bitmanipulation;
+
+/**
+ * Lowest Set Bit
+ * @author Prayas Kumar (https://github.com/prayas7102)
+ */
+
+public final class LowestSetBit {
+ // Private constructor to hide the default public one
+ private LowestSetBit() {
+ }
+ /**
+ * Isolates the lowest set bit of the given number. For example, if n = 18
+ * (binary: 10010), the result will be 2 (binary: 00010).
+ *
+ * @param n the number whose lowest set bit will be isolated
+ * @return the isolated lowest set bit of n
+ */
+ public static int isolateLowestSetBit(int n) {
+ // Isolate the lowest set bit using n & -n
+ return n & -n;
+ }
+ /**
+ * Clears the lowest set bit of the given number.
+ * For example, if n = 18 (binary: 10010), the result will be 16 (binary: 10000).
+ *
+ * @param n the number whose lowest set bit will be cleared
+ * @return the number after clearing its lowest set bit
+ */
+ public static int clearLowestSetBit(int n) {
+ // Clear the lowest set bit using n & (n - 1)
+ return n & (n - 1);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java b/src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java
new file mode 100644
index 000000000000..4c4d33640ad4
--- /dev/null
+++ b/src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java
@@ -0,0 +1,86 @@
+package com.thealgorithms.bitmanipulation;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test case for Lowest Set Bit
+ * @author Prayas Kumar (https://github.com/prayas7102)
+ */
+
+public class LowestSetBitTest {
+
+ @Test
+ void testLowestSetBitWithPositiveNumber() {
+ // Test with a general positive number
+ assertEquals(2, LowestSetBit.isolateLowestSetBit(18)); // 18 in binary: 10010, lowest bit is 2
+ }
+
+ @Test
+ void testLowestSetBitWithZero() {
+ // Test with zero (edge case, no set bits)
+ assertEquals(0, LowestSetBit.isolateLowestSetBit(0)); // 0 has no set bits, result should be 0
+ }
+
+ @Test
+ void testLowestSetBitWithOne() {
+ // Test with number 1 (lowest set bit is 1 itself)
+ assertEquals(1, LowestSetBit.isolateLowestSetBit(1)); // 1 in binary: 0001, lowest bit is 1
+ }
+
+ @Test
+ void testLowestSetBitWithPowerOfTwo() {
+ // Test with a power of two (only one set bit)
+ assertEquals(16, LowestSetBit.isolateLowestSetBit(16)); // 16 in binary: 10000, lowest bit is 16
+ }
+
+ @Test
+ void testLowestSetBitWithAllBitsSet() {
+ // Test with a number with multiple set bits (like 7)
+ assertEquals(1, LowestSetBit.isolateLowestSetBit(7)); // 7 in binary: 111, lowest bit is 1
+ }
+
+ @Test
+ void testLowestSetBitWithNegativeNumber() {
+ // Test with a negative number (-1 in two's complement has all bits set)
+ assertEquals(1, LowestSetBit.isolateLowestSetBit(-1)); // -1 in two's complement is all 1s, lowest bit is 1
+ }
+
+ @Test
+ void testLowestSetBitWithLargeNumber() {
+ // Test with a large number
+ assertEquals(64, LowestSetBit.isolateLowestSetBit(448)); // 448 in binary: 111000000, lowest bit is 64
+ }
+
+ @Test
+ void testClearLowestSetBitFor18() {
+ // n = 18 (binary: 10010), expected result = 16 (binary: 10000)
+ assertEquals(16, LowestSetBit.clearLowestSetBit(18));
+ }
+
+ @Test
+ void testClearLowestSetBitFor10() {
+ // n = 10 (binary: 1010), expected result = 8 (binary: 1000)
+ assertEquals(8, LowestSetBit.clearLowestSetBit(10));
+ }
+
+ @Test
+ void testClearLowestSetBitFor7() {
+ // n = 7 (binary: 0111), expected result = 6 (binary: 0110)
+ assertEquals(6, LowestSetBit.clearLowestSetBit(7));
+ }
+
+ @Test
+ void testClearLowestSetBitFor0() {
+ // n = 0 (binary: 0000), no set bits to clear, expected result = 0
+ assertEquals(0, LowestSetBit.clearLowestSetBit(0));
+ }
+
+ @Test
+ void testClearLowestSetBitForNegativeNumber() {
+ // Test negative number to see how it behaves with two's complement
+ // n = -1 (binary: all 1s in two's complement), expected result = -2 (clearing lowest set bit)
+ assertEquals(-2, LowestSetBit.clearLowestSetBit(-1));
+ }
+}
From c45b2b81324b1e374a56ddcc089b82c4da2b8f29 Mon Sep 17 00:00:00 2001
From: ShikariSohan <52916464+ShikariSohan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 18:21:39 +0600
Subject: [PATCH 087/457] Add line clipping algorithms (#5580)
---
.../lineclipping/CohenSutherland.java | 133 ++++++++++++++++++
.../lineclipping/LiangBarsky.java | 93 ++++++++++++
.../lineclipping/utils/Line.java | 43 ++++++
.../lineclipping/utils/Point.java | 43 ++++++
.../lineclipping/CohenSutherlandTest.java | 81 +++++++++++
.../lineclipping/LiangBarskyTest.java | 65 +++++++++
6 files changed, 458 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/lineclipping/CohenSutherland.java
create mode 100644 src/main/java/com/thealgorithms/lineclipping/LiangBarsky.java
create mode 100644 src/main/java/com/thealgorithms/lineclipping/utils/Line.java
create mode 100644 src/main/java/com/thealgorithms/lineclipping/utils/Point.java
create mode 100644 src/test/java/com/thealgorithms/lineclipping/CohenSutherlandTest.java
create mode 100644 src/test/java/com/thealgorithms/lineclipping/LiangBarskyTest.java
diff --git a/src/main/java/com/thealgorithms/lineclipping/CohenSutherland.java b/src/main/java/com/thealgorithms/lineclipping/CohenSutherland.java
new file mode 100644
index 000000000000..6e8611b86332
--- /dev/null
+++ b/src/main/java/com/thealgorithms/lineclipping/CohenSutherland.java
@@ -0,0 +1,133 @@
+package com.thealgorithms.lineclipping;
+
+import com.thealgorithms.lineclipping.utils.Line;
+import com.thealgorithms.lineclipping.utils.Point;
+
+/**
+ * @author shikarisohan
+ * @since 10/4/24
+ * Cohen-Sutherland Line Clipping Algorithm
+ *
+ * This algorithm is used to clip a line segment to a rectangular window.
+ * It assigns a region code to each endpoint of the line segment, and
+ * then efficiently determines whether the line segment is fully inside,
+ * fully outside, or partially inside the window.
+ *
+ * Reference:
+ * https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
+ *
+ * Clipping window boundaries are defined as (xMin, yMin) and (xMax, yMax).
+ * The algorithm computes the clipped line segment if it's partially or
+ * fully inside the clipping window.
+ */
+public class CohenSutherland {
+
+ // Region codes for the 9 regions
+ private static final int INSIDE = 0; // 0000
+ private static final int LEFT = 1; // 0001
+ private static final int RIGHT = 2; // 0010
+ private static final int BOTTOM = 4; // 0100
+ private static final int TOP = 8; // 1000
+
+ // Define the clipping window
+ double xMin;
+ double yMin;
+ double xMax;
+ double yMax;
+
+ public CohenSutherland(double xMin, double yMin, double xMax, double yMax) {
+ this.xMin = xMin;
+ this.yMin = yMin;
+ this.xMax = xMax;
+ this.yMax = yMax;
+ }
+
+ // Compute the region code for a point (x, y)
+ private int computeCode(double x, double y) {
+ int code = INSIDE;
+
+ if (x < xMin) // to the left of rectangle
+ {
+ code |= LEFT;
+ } else if (x > xMax) // to the right of rectangle
+ {
+ code |= RIGHT;
+ }
+ if (y < yMin) // below the rectangle
+ {
+ code |= BOTTOM;
+ } else if (y > yMax) // above the rectangle
+ {
+ code |= TOP;
+ }
+
+ return code;
+ }
+
+ // Cohen-Sutherland algorithm to return the clipped line
+ public Line cohenSutherlandClip(Line line) {
+ double x1 = line.start.x;
+ double y1 = line.start.y;
+ double x2 = line.end.x;
+ double y2 = line.end.y;
+
+ int code1 = computeCode(x1, y1);
+ int code2 = computeCode(x2, y2);
+ boolean accept = false;
+
+ while (true) {
+ if ((code1 == 0) && (code2 == 0)) {
+ // Both points are inside the rectangle
+ accept = true;
+ break;
+ } else if ((code1 & code2) != 0) {
+ // Both points are outside the rectangle in the same region
+ break;
+ } else {
+ // Some segment of the line is inside the rectangle
+ double x = 0;
+ double y = 0;
+
+ // Pick an endpoint that is outside the rectangle
+ int codeOut = (code1 != 0) ? code1 : code2;
+
+ // Find the intersection point using the line equation
+ if ((codeOut & TOP) != 0) {
+ // Point is above the rectangle
+ x = x1 + (x2 - x1) * (yMax - y1) / (y2 - y1);
+ y = yMax;
+ } else if ((codeOut & BOTTOM) != 0) {
+ // Point is below the rectangle
+ x = x1 + (x2 - x1) * (yMin - y1) / (y2 - y1);
+ y = yMin;
+ } else if ((codeOut & RIGHT) != 0) {
+ // Point is to the right of the rectangle
+ y = y1 + (y2 - y1) * (xMax - x1) / (x2 - x1);
+ x = xMax;
+ } else if ((codeOut & LEFT) != 0) {
+ // Point is to the left of the rectangle
+ y = y1 + (y2 - y1) * (xMin - x1) / (x2 - x1);
+ x = xMin;
+ }
+
+ // Replace the point outside the rectangle with the intersection point
+ if (codeOut == code1) {
+ x1 = x;
+ y1 = y;
+ code1 = computeCode(x1, y1);
+ } else {
+ x2 = x;
+ y2 = y;
+ code2 = computeCode(x2, y2);
+ }
+ }
+ }
+
+ if (accept) {
+ return new Line(new Point(x1, y1), new Point(x2, y2));
+ } else {
+
+ return null; // The line is fully rejected
+ }
+ }
+}
diff --git a/src/main/java/com/thealgorithms/lineclipping/LiangBarsky.java b/src/main/java/com/thealgorithms/lineclipping/LiangBarsky.java
new file mode 100644
index 000000000000..723e2bb2fbf9
--- /dev/null
+++ b/src/main/java/com/thealgorithms/lineclipping/LiangBarsky.java
@@ -0,0 +1,93 @@
+package com.thealgorithms.lineclipping;
+
+import com.thealgorithms.lineclipping.utils.Line;
+import com.thealgorithms.lineclipping.utils.Point;
+
+/**
+ * @author shikarisohan
+ * @since 10/5/24
+ *
+ * * The Liang-Barsky line clipping algorithm is an efficient algorithm for
+ * * line clipping against a rectangular window. It is based on the parametric
+ * * equation of a line and checks the intersections of the line with the
+ * * window boundaries. This algorithm calculates the intersection points,
+ * * if any, and returns the clipped line that lies inside the window.
+ * *
+ * * Reference:
+ * * https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm
+ *
+ * Clipping window boundaries are defined as (xMin, yMin) and (xMax, yMax).
+ * The algorithm computes the clipped line segment if it's partially or
+ * fully inside the clipping window.
+ */
+public class LiangBarsky {
+
+ // Define the clipping window
+ double xMin;
+ double xMax;
+ double yMin;
+ double yMax;
+
+ public LiangBarsky(double xMin, double yMin, double xMax, double yMax) {
+ this.xMin = xMin;
+ this.yMin = yMin;
+ this.xMax = xMax;
+ this.yMax = yMax;
+ }
+
+ // Liang-Barsky algorithm to return the clipped line
+ public Line liangBarskyClip(Line line) {
+ double dx = line.end.x - line.start.x;
+ double dy = line.end.y - line.start.y;
+
+ double[] p = {-dx, dx, -dy, dy};
+ double[] q = {line.start.x - xMin, xMax - line.start.x, line.start.y - yMin, yMax - line.start.y};
+
+ double[] resultT = clipLine(p, q);
+
+ if (resultT == null) {
+ return null; // Line is outside the clipping window
+ }
+
+ return calculateClippedLine(line, resultT[0], resultT[1], dx, dy);
+ }
+
+ // clip the line by adjusting t0 and t1 for each edge
+ private double[] clipLine(double[] p, double[] q) {
+ double t0 = 0.0;
+ double t1 = 1.0;
+
+ for (int i = 0; i < 4; i++) {
+ double t = q[i] / p[i];
+ if (p[i] == 0 && q[i] < 0) {
+ return null; // Line is outside the boundary
+ } else if (p[i] < 0) {
+ if (t > t1) {
+ return null;
+ } // Line is outside
+ if (t > t0) {
+ t0 = t;
+ } // Update t0
+ } else if (p[i] > 0) {
+ if (t < t0) {
+ return null;
+ } // Line is outside
+ if (t < t1) {
+ t1 = t;
+ } // Update t1
+ }
+ }
+
+ return new double[] {t0, t1}; // Return valid t0 and t1
+ }
+
+ // calculate the clipped line based on t0 and t1
+ private Line calculateClippedLine(Line line, double t0, double t1, double dx, double dy) {
+ double clippedX1 = line.start.x + t0 * dx;
+ double clippedY1 = line.start.y + t0 * dy;
+ double clippedX2 = line.start.x + t1 * dx;
+ double clippedY2 = line.start.y + t1 * dy;
+
+ return new Line(new Point(clippedX1, clippedY1), new Point(clippedX2, clippedY2));
+ }
+}
diff --git a/src/main/java/com/thealgorithms/lineclipping/utils/Line.java b/src/main/java/com/thealgorithms/lineclipping/utils/Line.java
new file mode 100644
index 000000000000..56cd52e3cdce
--- /dev/null
+++ b/src/main/java/com/thealgorithms/lineclipping/utils/Line.java
@@ -0,0 +1,43 @@
+package com.thealgorithms.lineclipping.utils;
+
+import java.util.Objects;
+
+/**
+ * @author moksedursohan
+ * @since 10/4/24
+ */
+public class Line {
+
+ public Point start;
+ public Point end;
+
+ public Line() {
+ }
+
+ public Line(Point start, Point end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Line line)) {
+ return false;
+ }
+
+ return Objects.equals(start, line.start) && Objects.equals(end, line.end);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return "Line from " + start + " to " + end;
+ }
+}
diff --git a/src/main/java/com/thealgorithms/lineclipping/utils/Point.java b/src/main/java/com/thealgorithms/lineclipping/utils/Point.java
new file mode 100644
index 000000000000..7ef58c783903
--- /dev/null
+++ b/src/main/java/com/thealgorithms/lineclipping/utils/Point.java
@@ -0,0 +1,43 @@
+package com.thealgorithms.lineclipping.utils;
+
+import java.util.Objects;
+
+/**
+ * @author moksedursohan
+ * @since 10/4/24
+ */
+public class Point {
+
+ public double x;
+ public double y;
+
+ public Point() {
+ }
+
+ public Point(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Point point)) {
+ return false;
+ }
+
+ return Double.compare(x, point.x) == 0 && Double.compare(y, point.y) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(x, y);
+ }
+
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ")";
+ }
+}
diff --git a/src/test/java/com/thealgorithms/lineclipping/CohenSutherlandTest.java b/src/test/java/com/thealgorithms/lineclipping/CohenSutherlandTest.java
new file mode 100644
index 000000000000..064f71a17c12
--- /dev/null
+++ b/src/test/java/com/thealgorithms/lineclipping/CohenSutherlandTest.java
@@ -0,0 +1,81 @@
+package com.thealgorithms.lineclipping;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.thealgorithms.lineclipping.utils.Line;
+import com.thealgorithms.lineclipping.utils.Point;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author shikarisohan
+ * @since 10/4/24
+ */
+class CohenSutherlandTest {
+
+ // Define the clipping window (1.0, 1.0) to (10.0, 10.0)
+ CohenSutherland cs = new CohenSutherland(1.0, 1.0, 10.0, 10.0);
+
+ @Test
+ void testLineCompletelyInside() {
+ // Line fully inside the clipping window
+ Line line = new Line(new Point(2.0, 2.0), new Point(8.0, 8.0));
+ Line clippedLine = cs.cohenSutherlandClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(line, clippedLine, "Line inside the window should remain unchanged.");
+ }
+
+ @Test
+ void testLineCompletelyOutside() {
+ // Line completely outside and above the clipping window
+ Line line = new Line(new Point(11.0, 12.0), new Point(15.0, 18.0));
+ Line clippedLine = cs.cohenSutherlandClip(line);
+
+ assertNull(clippedLine, "Line should be null because it's completely outside.");
+ }
+
+ @Test
+ void testLinePartiallyInside() {
+ // Line partially inside the clipping window
+ Line line = new Line(new Point(5.0, 5.0), new Point(12.0, 12.0));
+ Line expectedClippedLine = new Line(new Point(5.0, 5.0), new Point(10.0, 10.0)); // Clipped at (10, 10)
+ Line clippedLine = cs.cohenSutherlandClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(expectedClippedLine, clippedLine, "Line should be clipped correctly.");
+ }
+
+ @Test
+ void testLineOnBoundary() {
+ // Line exactly on the boundary of the clipping window
+ Line line = new Line(new Point(1.0, 5.0), new Point(10.0, 5.0));
+ Line clippedLine = cs.cohenSutherlandClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(line, clippedLine, "Line on the boundary should remain unchanged.");
+ }
+
+ @Test
+ void testDiagonalLineThroughClippingWindow() {
+ // Diagonal line crossing from outside to outside through the window
+ Line line = new Line(new Point(0.0, 0.0), new Point(12.0, 12.0));
+ Line expectedClippedLine = new Line(new Point(1.0, 1.0), new Point(10.0, 10.0)); // Clipped at both boundaries
+ Line clippedLine = cs.cohenSutherlandClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(expectedClippedLine, clippedLine, "Diagonal line should be clipped correctly.");
+ }
+
+ @Test
+ void testVerticalLineClipping() {
+ // Vertical line crossing the top and bottom of the clipping window
+ Line line = new Line(new Point(5.0, 0.0), new Point(5.0, 12.0));
+ Line expectedClippedLine = new Line(new Point(5.0, 1.0), new Point(5.0, 10.0)); // Clipped at yMin and yMax
+ Line clippedLine = cs.cohenSutherlandClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(expectedClippedLine, clippedLine, "Vertical line should be clipped correctly.");
+ }
+}
diff --git a/src/test/java/com/thealgorithms/lineclipping/LiangBarskyTest.java b/src/test/java/com/thealgorithms/lineclipping/LiangBarskyTest.java
new file mode 100644
index 000000000000..1c48cd106572
--- /dev/null
+++ b/src/test/java/com/thealgorithms/lineclipping/LiangBarskyTest.java
@@ -0,0 +1,65 @@
+package com.thealgorithms.lineclipping;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.thealgorithms.lineclipping.utils.Line;
+import com.thealgorithms.lineclipping.utils.Point;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author shikarisohan
+ * @since 10/5/24
+ */
+class LiangBarskyTest {
+
+ LiangBarsky lb = new LiangBarsky(1.0, 1.0, 10.0, 10.0);
+
+ @Test
+ void testLineCompletelyInside() {
+ Line line = new Line(new Point(2.0, 2.0), new Point(8.0, 8.0));
+ Line clippedLine = lb.liangBarskyClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(line, clippedLine, "Line inside the window should remain unchanged.");
+ }
+
+ @Test
+ void testLineCompletelyOutside() {
+ Line line = new Line(new Point(12.0, 12.0), new Point(15.0, 18.0));
+ Line clippedLine = lb.liangBarskyClip(line);
+
+ assertNull(clippedLine, "Line should be null because it's completely outside.");
+ }
+
+ @Test
+ void testLinePartiallyInside() {
+ Line line = new Line(new Point(5.0, 5.0), new Point(12.0, 12.0));
+ Line expectedClippedLine = new Line(new Point(5.0, 5.0), new Point(10.0, 10.0)); // Clipped at (10, 10)
+ Line clippedLine = lb.liangBarskyClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(expectedClippedLine, clippedLine, "Line should be clipped correctly.");
+ }
+
+ @Test
+ void testDiagonalLineThroughClippingWindow() {
+ Line line = new Line(new Point(0.0, 0.0), new Point(12.0, 12.0));
+ Line expectedClippedLine = new Line(new Point(1.0, 1.0), new Point(10.0, 10.0)); // Clipped at both boundaries
+ Line clippedLine = lb.liangBarskyClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(expectedClippedLine, clippedLine, "Diagonal line should be clipped correctly.");
+ }
+
+ @Test
+ void testVerticalLineClipping() {
+ Line line = new Line(new Point(5.0, 0.0), new Point(5.0, 12.0));
+ Line expectedClippedLine = new Line(new Point(5.0, 1.0), new Point(5.0, 10.0)); // Clipped at yMin and yMax
+ Line clippedLine = lb.liangBarskyClip(line);
+
+ assertNotNull(clippedLine, "Line should not be null.");
+ assertEquals(expectedClippedLine, clippedLine, "Vertical line should be clipped correctly.");
+ }
+}
From 2592a088e7759675224a6d6ba608aa9019b15bd4 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:56:21 +0530
Subject: [PATCH 088/457] Enhance readability, add comments & function docs to
SkylineProblem.java (#5534)
---
DIRECTORY.md | 12 +++
.../thealgorithms/others/SkylineProblem.java | 99 +++++++++++--------
.../others/SkylineProblemTest.java | 86 ++++++++++++++++
3 files changed, 154 insertions(+), 43 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/others/SkylineProblemTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 6b44fa336a3a..0d34492fde9a 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -27,6 +27,7 @@
* [IndexOfRightMostSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java)
* [IsEven](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsEven.java)
* [IsPowerTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java)
+ * [LowestSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java)
* [NonRepeatingNumberFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java)
* [NumbersDifferentSigns](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java)
* [ReverseBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java)
@@ -280,6 +281,12 @@
* [MinimizingLateness](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimizingLateness.java)
* io
* [BufferedReader](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/io/BufferedReader.java)
+ * lineclipping
+ * [CohenSutherland](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/CohenSutherland.java)
+ * [LiangBarsky](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/LiangBarsky.java)
+ * utils
+ * [Line](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/utils/Line.java)
+ * [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/utils/Point.java)
* maths
* [AbsoluteMax](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AbsoluteMax.java)
* [AbsoluteMin](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AbsoluteMin.java)
@@ -615,6 +622,7 @@
* [IndexOfRightMostSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBitTest.java)
* [IsEvenTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsEvenTest.java)
* [IsPowerTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java)
+ * [LowestSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java)
* [NonRepeatingNumberFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java)
* [NumbersDifferentSignsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java)
* [ReverseBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java)
@@ -785,6 +793,9 @@
* [MinimizingLatenessTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimizingLatenessTest.java)
* io
* [BufferedReaderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/io/BufferedReaderTest.java)
+ * lineclipping
+ * [CohenSutherlandTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/lineclipping/CohenSutherlandTest.java)
+ * [LiangBarskyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/lineclipping/LiangBarskyTest.java)
* maths
* [AbsoluteMaxTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java)
* [AbsoluteMinTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java)
@@ -912,6 +923,7 @@
* [QueueUsingTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/QueueUsingTwoStacksTest.java)
* [RemoveDuplicateFromStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/RemoveDuplicateFromStringTest.java)
* [ReverseStackUsingRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ReverseStackUsingRecursionTest.java)
+ * [SkylineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/SkylineProblemTest.java)
* [TestPrintMatrixInSpiralOrder](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.java)
* [TwoPointersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/TwoPointersTest.java)
* [WorstFitCPUTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/WorstFitCPUTest.java)
diff --git a/src/main/java/com/thealgorithms/others/SkylineProblem.java b/src/main/java/com/thealgorithms/others/SkylineProblem.java
index ece398e70405..e84a5c5b585b 100644
--- a/src/main/java/com/thealgorithms/others/SkylineProblem.java
+++ b/src/main/java/com/thealgorithms/others/SkylineProblem.java
@@ -1,69 +1,68 @@
package com.thealgorithms.others;
import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Scanner;
+/**
+ * The {@code SkylineProblem} class is used to solve the skyline problem using a
+ * divide-and-conquer approach.
+ * It reads input for building data, processes it to find the skyline, and
+ * prints the skyline.
+ */
public class SkylineProblem {
Building[] building;
int count;
- public void run() {
- Scanner sc = new Scanner(System.in);
-
- int num = sc.nextInt();
- this.building = new Building[num];
-
- for (int i = 0; i < num; i++) {
- String input = sc.next();
- String[] data = input.split(",");
- this.add(Integer.parseInt(data[0]), Integer.parseInt(data[1]), Integer.parseInt(data[2]));
- }
- this.print(this.findSkyline(0, num - 1));
-
- sc.close();
- }
-
+ /**
+ * Adds a building with the given left, height, and right values to the
+ * buildings list.
+ *
+ * @param left The left x-coordinate of the building.
+ * @param height The height of the building.
+ * @param right The right x-coordinate of the building.
+ */
public void add(int left, int height, int right) {
building[count++] = new Building(left, height, right);
}
- public void print(ArrayList skyline) {
- Iterator it = skyline.iterator();
-
- while (it.hasNext()) {
- Skyline temp = it.next();
- System.out.print(temp.coordinates + "," + temp.height);
- if (it.hasNext()) {
- System.out.print(",");
- }
- }
- }
-
+ /**
+ * Computes the skyline for a range of buildings using the divide-and-conquer
+ * strategy.
+ *
+ * @param start The starting index of the buildings to process.
+ * @param end The ending index of the buildings to process.
+ * @return A list of {@link Skyline} objects representing the computed skyline.
+ */
public ArrayList findSkyline(int start, int end) {
+ // Base case: only one building, return its skyline.
if (start == end) {
ArrayList list = new ArrayList<>();
list.add(new Skyline(building[start].left, building[start].height));
- list.add(new Skyline(building[end].right, 0));
-
+ list.add(new Skyline(building[end].right, 0)); // Add the end of the building
return list;
}
int mid = (start + end) / 2;
- ArrayList sky1 = this.findSkyline(start, mid);
- ArrayList sky2 = this.findSkyline(mid + 1, end);
-
- return this.mergeSkyline(sky1, sky2);
+ ArrayList sky1 = this.findSkyline(start, mid); // Find the skyline of the left half
+ ArrayList sky2 = this.findSkyline(mid + 1, end); // Find the skyline of the right half
+ return this.mergeSkyline(sky1, sky2); // Merge the two skylines
}
+ /**
+ * Merges two skylines (sky1 and sky2) into one combined skyline.
+ *
+ * @param sky1 The first skyline list.
+ * @param sky2 The second skyline list.
+ * @return A list of {@link Skyline} objects representing the merged skyline.
+ */
public ArrayList mergeSkyline(ArrayList sky1, ArrayList sky2) {
int currentH1 = 0;
int currentH2 = 0;
ArrayList skyline = new ArrayList<>();
int maxH = 0;
+ // Merge the two skylines
while (!sky1.isEmpty() && !sky2.isEmpty()) {
if (sky1.get(0).coordinates < sky2.get(0).coordinates) {
int currentX = sky1.get(0).coordinates;
@@ -96,6 +95,7 @@ public ArrayList mergeSkyline(ArrayList sky1, ArrayList mergeSkyline(ArrayList sky1, ArrayList result = skylineProblem.findSkyline(0, 0);
+
+ assertEquals(2, result.get(0).coordinates);
+ assertEquals(10, result.get(0).height);
+ assertEquals(9, result.get(1).coordinates);
+ assertEquals(0, result.get(1).height);
+ }
+
+ @Test
+ public void testTwoBuildingsSkyline() {
+ SkylineProblem skylineProblem = new SkylineProblem();
+ skylineProblem.building = new SkylineProblem.Building[2];
+ skylineProblem.add(1, 11, 5);
+ skylineProblem.add(2, 6, 7);
+
+ ArrayList result = skylineProblem.findSkyline(0, 1);
+
+ // Expected skyline points: (1, 11), (5, 6), (7, 0)
+ assertEquals(1, result.get(0).coordinates);
+ assertEquals(11, result.get(0).height);
+ assertEquals(5, result.get(1).coordinates);
+ assertEquals(6, result.get(1).height);
+ assertEquals(7, result.get(2).coordinates);
+ assertEquals(0, result.get(2).height);
+ }
+
+ @Test
+ public void testMergeSkyline() {
+ SkylineProblem skylineProblem = new SkylineProblem();
+ ArrayList sky1 = new ArrayList<>();
+ ArrayList sky2 = new ArrayList<>();
+
+ sky1.add(skylineProblem.new Skyline(2, 10));
+ sky1.add(skylineProblem.new Skyline(9, 0));
+
+ sky2.add(skylineProblem.new Skyline(3, 15));
+ sky2.add(skylineProblem.new Skyline(7, 0));
+
+ ArrayList result = skylineProblem.mergeSkyline(sky1, sky2);
+
+ // Expected merged skyline: (2, 10), (3, 15), (7, 10), (9, 0)
+ assertEquals(2, result.get(0).coordinates);
+ assertEquals(10, result.get(0).height);
+ assertEquals(3, result.get(1).coordinates);
+ assertEquals(15, result.get(1).height);
+ assertEquals(7, result.get(2).coordinates);
+ assertEquals(10, result.get(2).height);
+ assertEquals(9, result.get(3).coordinates);
+ assertEquals(0, result.get(3).height);
+ }
+
+ @Test
+ public void testMultipleBuildingsSkyline() {
+ SkylineProblem skylineProblem = new SkylineProblem();
+ skylineProblem.building = new SkylineProblem.Building[3];
+ skylineProblem.add(1, 10, 5);
+ skylineProblem.add(2, 15, 7);
+ skylineProblem.add(3, 12, 9);
+
+ ArrayList result = skylineProblem.findSkyline(0, 2);
+
+ assertEquals(1, result.get(0).coordinates);
+ assertEquals(10, result.get(0).height);
+ assertEquals(2, result.get(1).coordinates);
+ assertEquals(15, result.get(1).height);
+ assertEquals(7, result.get(2).coordinates);
+ assertEquals(12, result.get(2).height);
+ assertEquals(9, result.get(3).coordinates);
+ assertEquals(0, result.get(3).height);
+ }
+}
From fa7d35745101851ed46bf9a3f813e495ef1f2841 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 18:36:59 +0530
Subject: [PATCH 089/457] Add tests, enhance class & function documentation for
KnightsTour (#5591)
---
DIRECTORY.md | 1 +
.../backtracking/KnightsTour.java | 139 +++++++++---------
.../backtracking/KnightsTourTest.java | 59 ++++++++
3 files changed, 133 insertions(+), 66 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 0d34492fde9a..23544efba24c 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -607,6 +607,7 @@
* [ArrayCombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java)
* [CombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/CombinationTest.java)
* [FloodFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/FloodFillTest.java)
+ * [KnightsTourTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java)
* [MazeRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java)
* [MColoringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MColoringTest.java)
* [NQueensTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/NQueensTest.java)
diff --git a/src/main/java/com/thealgorithms/backtracking/KnightsTour.java b/src/main/java/com/thealgorithms/backtracking/KnightsTour.java
index 2287b39da385..2c2da659f3aa 100644
--- a/src/main/java/com/thealgorithms/backtracking/KnightsTour.java
+++ b/src/main/java/com/thealgorithms/backtracking/KnightsTour.java
@@ -4,33 +4,26 @@
import java.util.Comparator;
import java.util.List;
-/*
- * Problem Statement: -
-
- Given a N*N board with the Knight placed on the first block of an empty board. Moving according
- to the rules of chess knight must visit each square exactly once. Print the order of each cell in
- which they are visited.
-
- Example: -
-
- Input : N = 8
-
- Output:
- 0 59 38 33 30 17 8 63
- 37 34 31 60 9 62 29 16
- 58 1 36 39 32 27 18 7
- 35 48 41 26 61 10 15 28
- 42 57 2 49 40 23 6 19
- 47 50 45 54 25 20 11 14
- 56 43 52 3 22 13 24 5
- 51 46 55 44 53 4 21 12
-
+/**
+ * The KnightsTour class solves the Knight's Tour problem using backtracking.
+ *
+ * Problem Statement:
+ * Given an N*N board with a knight placed on the first block, the knight must
+ * move according to chess rules and visit each square on the board exactly once.
+ * The class outputs the sequence of moves for the knight.
+ *
+ * Example:
+ * Input: N = 8 (8x8 chess board)
+ * Output: The sequence of numbers representing the order in which the knight visits each square.
*/
public final class KnightsTour {
private KnightsTour() {
}
+ // The size of the chess board (12x12 grid, with 2 extra rows/columns as a buffer around a 8x8 area)
private static final int BASE = 12;
+
+ // Possible moves for a knight in chess
private static final int[][] MOVES = {
{1, -2},
{2, -1},
@@ -40,36 +33,40 @@ private KnightsTour() {
{-2, 1},
{-2, -1},
{-1, -2},
- }; // Possible moves by knight on chess
- private static int[][] grid; // chess grid
- private static int total; // total squares in chess
+ };
+
+ // Chess grid representing the board
+ static int[][] grid;
+
+ // Total number of cells the knight needs to visit
+ static int total;
- public static void main(String[] args) {
+ /**
+ * Resets the chess board to its initial state.
+ * Initializes the grid with boundary cells marked as -1 and internal cells as 0.
+ * Sets the total number of cells the knight needs to visit.
+ */
+ public static void resetBoard() {
grid = new int[BASE][BASE];
total = (BASE - 4) * (BASE - 4);
-
for (int r = 0; r < BASE; r++) {
for (int c = 0; c < BASE; c++) {
if (r < 2 || r > BASE - 3 || c < 2 || c > BASE - 3) {
- grid[r][c] = -1;
+ grid[r][c] = -1; // Mark boundary cells
}
}
}
-
- int row = 2 + (int) (Math.random() * (BASE - 4));
- int col = 2 + (int) (Math.random() * (BASE - 4));
-
- grid[row][col] = 1;
-
- if (solve(row, col, 2)) {
- printResult();
- } else {
- System.out.println("no result");
- }
}
- // Return True when solvable
- private static boolean solve(int row, int column, int count) {
+ /**
+ * Recursive method to solve the Knight's Tour problem.
+ *
+ * @param row The current row of the knight
+ * @param column The current column of the knight
+ * @param count The current move number
+ * @return True if a solution is found, False otherwise
+ */
+ static boolean solve(int row, int column, int count) {
if (count > total) {
return true;
}
@@ -80,29 +77,37 @@ private static boolean solve(int row, int column, int count) {
return false;
}
+ // Sort neighbors by Warnsdorff's rule (fewest onward moves)
neighbor.sort(Comparator.comparingInt(a -> a[2]));
for (int[] nb : neighbor) {
- row = nb[0];
- column = nb[1];
- grid[row][column] = count;
- if (!orphanDetected(count, row, column) && solve(row, column, count + 1)) {
+ int nextRow = nb[0];
+ int nextCol = nb[1];
+ grid[nextRow][nextCol] = count;
+ if (!orphanDetected(count, nextRow, nextCol) && solve(nextRow, nextCol, count + 1)) {
return true;
}
- grid[row][column] = 0;
+ grid[nextRow][nextCol] = 0; // Backtrack
}
return false;
}
- // Returns List of neighbours
- private static List neighbors(int row, int column) {
+ /**
+ * Returns a list of valid neighboring cells where the knight can move.
+ *
+ * @param row The current row of the knight
+ * @param column The current column of the knight
+ * @return A list of arrays representing valid moves, where each array contains:
+ * {nextRow, nextCol, numberOfPossibleNextMoves}
+ */
+ static List neighbors(int row, int column) {
List neighbour = new ArrayList<>();
for (int[] m : MOVES) {
int x = m[0];
int y = m[1];
- if (grid[row + y][column + x] == 0) {
+ if (row + y >= 0 && row + y < BASE && column + x >= 0 && column + x < BASE && grid[row + y][column + x] == 0) {
int num = countNeighbors(row + y, column + x);
neighbour.add(new int[] {row + y, column + x, num});
}
@@ -110,19 +115,34 @@ private static List neighbors(int row, int column) {
return neighbour;
}
- // Returns the total count of neighbors
- private static int countNeighbors(int row, int column) {
+ /**
+ * Counts the number of possible valid moves for a knight from a given position.
+ *
+ * @param row The row of the current position
+ * @param column The column of the current position
+ * @return The number of valid neighboring moves
+ */
+ static int countNeighbors(int row, int column) {
int num = 0;
for (int[] m : MOVES) {
- if (grid[row + m[1]][column + m[0]] == 0) {
+ int x = m[0];
+ int y = m[1];
+ if (row + y >= 0 && row + y < BASE && column + x >= 0 && column + x < BASE && grid[row + y][column + x] == 0) {
num++;
}
}
return num;
}
- // Returns true if it is orphan
- private static boolean orphanDetected(int count, int row, int column) {
+ /**
+ * Detects if moving to a given position will create an orphan (a position with no further valid moves).
+ *
+ * @param count The current move number
+ * @param row The row of the current position
+ * @param column The column of the current position
+ * @return True if an orphan is detected, False otherwise
+ */
+ static boolean orphanDetected(int count, int row, int column) {
if (count < total - 1) {
List neighbor = neighbors(row, column);
for (int[] nb : neighbor) {
@@ -133,17 +153,4 @@ private static boolean orphanDetected(int count, int row, int column) {
}
return false;
}
-
- // Prints the result grid
- private static void printResult() {
- for (int[] row : grid) {
- for (int i : row) {
- if (i == -1) {
- continue;
- }
- System.out.printf("%2d ", i);
- }
- System.out.println();
- }
- }
}
diff --git a/src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java b/src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java
new file mode 100644
index 000000000000..306dbf4c2ec7
--- /dev/null
+++ b/src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java
@@ -0,0 +1,59 @@
+package com.thealgorithms.backtracking;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class KnightsTourTest {
+
+ @BeforeEach
+ void setUp() {
+ // Call the reset method in the KnightsTour class
+ KnightsTour.resetBoard();
+ }
+
+ @Test
+ void testGridInitialization() {
+ for (int r = 0; r < 12; r++) {
+ for (int c = 0; c < 12; c++) {
+ if (r < 2 || r > 12 - 3 || c < 2 || c > 12 - 3) {
+ assertEquals(-1, KnightsTour.grid[r][c], "Border cells should be -1");
+ } else {
+ assertEquals(0, KnightsTour.grid[r][c], "Internal cells should be 0");
+ }
+ }
+ }
+ }
+
+ @Test
+ void testCountNeighbors() {
+ // Manually place a knight at (3, 3) and mark nearby cells to test counting
+ KnightsTour.grid[3][3] = 1; // Knight is here
+ KnightsTour.grid[5][4] = -1; // Block one potential move
+
+ int neighborCount = KnightsTour.countNeighbors(3, 3);
+ assertEquals(3, neighborCount, "Knight at (3, 3) should have 3 neighbors (one blocked)");
+
+ KnightsTour.grid[4][1] = -1; // Block another move
+ neighborCount = KnightsTour.countNeighbors(3, 3);
+ assertEquals(3, neighborCount, "Knight at (3, 3) should have 3 neighbors (two blocked)");
+ }
+
+ @Test
+ void testNeighbors() {
+ // Test the list of valid neighbors for a given cell (3, 3)
+ List neighbors = KnightsTour.neighbors(3, 3);
+ assertEquals(4, neighbors.size(), "Knight at (3, 3) should have 8 valid neighbors");
+ }
+
+ @Test
+ void testSolveSuccessful() {
+ // Test if the solve method works for a successful knight's tour
+ KnightsTour.grid[2][2] = 1; // Start the knight at (2, 2)
+ boolean result = KnightsTour.solve(2, 2, 2);
+ assertTrue(result, "solve() should successfully complete a Knight's tour");
+ }
+}
From a2457bd1ff3cd0e55db1c4203a927b0c309b8bfe Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 18:47:27 +0530
Subject: [PATCH 090/457] Add tests for `IIRFilter.java`, fix bug in
`setCoeffs` method (#5590)
---
DIRECTORY.md | 2 +
.../thealgorithms/audiofilters/IIRFilter.java | 2 +-
.../audiofilters/IIRFilterTest.java | 83 +++++++++++++++++++
3 files changed, 86 insertions(+), 1 deletion(-)
create mode 100644 src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 23544efba24c..19ebe5c4b8e0 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -602,6 +602,8 @@
* java
* com
* thealgorithms
+ * audiofilters
+ * [IIRFilterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java)
* backtracking
* [AllPathsFromSourceToTargetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/AllPathsFromSourceToTargetTest.java)
* [ArrayCombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java)
diff --git a/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java b/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java
index c211cd08a501..fbc095909541 100644
--- a/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java
+++ b/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java
@@ -58,7 +58,7 @@ public void setCoeffs(double[] aCoeffs, double[] bCoeffs) throws IllegalArgument
throw new IllegalArgumentException("bCoeffs must be of size " + order + ", got " + bCoeffs.length);
}
- for (int i = 0; i <= order; i++) {
+ for (int i = 0; i < order; i++) {
coeffsA[i] = aCoeffs[i];
coeffsB[i] = bCoeffs[i];
}
diff --git a/src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java b/src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java
new file mode 100644
index 000000000000..66d7d60c501b
--- /dev/null
+++ b/src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java
@@ -0,0 +1,83 @@
+package com.thealgorithms.audiofilters;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class IIRFilterTest {
+
+ @Test
+ void testConstructorValidOrder() {
+ // Test a valid filter creation
+ IIRFilter filter = new IIRFilter(2);
+ assertNotNull(filter, "Filter should be instantiated correctly");
+ }
+
+ @Test
+ void testConstructorInvalidOrder() {
+ // Test an invalid filter creation (order <= 0)
+ assertThrows(IllegalArgumentException.class, () -> { new IIRFilter(0); }, "Order must be greater than zero");
+ }
+
+ @Test
+ void testSetCoeffsInvalidLengthA() {
+ IIRFilter filter = new IIRFilter(2);
+
+ // Invalid 'aCoeffs' length
+ double[] aCoeffs = {1.0}; // too short
+ double[] bCoeffs = {1.0, 0.5};
+ assertThrows(IllegalArgumentException.class, () -> { filter.setCoeffs(aCoeffs, bCoeffs); }, "aCoeffs must be of size 2");
+ }
+
+ @Test
+ void testSetCoeffsInvalidLengthB() {
+ IIRFilter filter = new IIRFilter(2);
+
+ // Invalid 'bCoeffs' length
+ double[] aCoeffs = {1.0, 0.5};
+ double[] bCoeffs = {1.0}; // too short
+ assertThrows(IllegalArgumentException.class, () -> { filter.setCoeffs(aCoeffs, bCoeffs); }, "bCoeffs must be of size 2");
+ }
+
+ @Test
+ void testSetCoeffsInvalidACoeffZero() {
+ IIRFilter filter = new IIRFilter(2);
+
+ // Invalid 'aCoeffs' where aCoeffs[0] == 0.0
+ double[] aCoeffs = {0.0, 0.5}; // aCoeffs[0] must not be zero
+ double[] bCoeffs = {1.0, 0.5};
+ assertThrows(IllegalArgumentException.class, () -> { filter.setCoeffs(aCoeffs, bCoeffs); }, "aCoeffs[0] must not be zero");
+ }
+
+ @Test
+ void testProcessWithNoCoeffsSet() {
+ // Test process method with default coefficients (sane defaults)
+ IIRFilter filter = new IIRFilter(2);
+ double inputSample = 0.5;
+ double result = filter.process(inputSample);
+
+ // Since default coeffsA[0] and coeffsB[0] are 1.0, expect output = input
+ assertEquals(inputSample, result, 1e-6, "Process should return the same value as input with default coefficients");
+ }
+
+ @Test
+ void testProcessWithCoeffsSet() {
+ // Test process method with set coefficients
+ IIRFilter filter = new IIRFilter(2);
+
+ double[] aCoeffs = {1.0, 0.5};
+ double[] bCoeffs = {1.0, 0.5};
+ filter.setCoeffs(aCoeffs, bCoeffs);
+
+ // Process a sample
+ double inputSample = 0.5;
+ double result = filter.process(inputSample);
+
+ // Expected output can be complex to calculate in advance;
+ // check if the method runs and returns a result within reasonable bounds
+ assertTrue(result >= -1.0 && result <= 1.0, "Processed result should be in the range [-1, 1]");
+ }
+}
From cacd23a2bde1da89393cab89cd519ffdf1af56db Mon Sep 17 00:00:00 2001
From: Muhammad Junaid Khalid
Date: Mon, 7 Oct 2024 19:17:04 +0500
Subject: [PATCH 091/457] Add binary addition (#5593)
---
DIRECTORY.md | 2 +
.../greedyalgorithms/BinaryAddition.java | 75 +++++++++++++++
.../greedyalgorithms/BinaryAdditionTest.java | 96 +++++++++++++++++++
3 files changed, 173 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java
create mode 100644 src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 19ebe5c4b8e0..2b4dc078c704 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -272,6 +272,7 @@
* [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java)
* greedyalgorithms
* [ActivitySelection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java)
+ * [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java)
* [CoinChange](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java)
* [DigitSeparation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/DigitSeparation.java)
* [FractionalKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java)
@@ -787,6 +788,7 @@
* [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java)
* greedyalgorithms
* [ActivitySelectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java)
+ * [BinaryAdditionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java)
* [CoinChangeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java)
* [DigitSeparationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/DigitSeparationTest.java)
* [FractionalKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/FractionalKnapsackTest.java)
diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java b/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java
new file mode 100644
index 000000000000..074c76b9f33f
--- /dev/null
+++ b/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java
@@ -0,0 +1,75 @@
+package com.thealgorithms.greedyalgorithms;
+
+import java.util.Collections;
+
+/**
+ * BinaryAddition class to perform binary addition of two binary strings.
+ */
+public class BinaryAddition {
+ /**
+ * Computes the sum of two binary characters and a carry.
+ * @param a First binary character ('0' or '1').
+ * @param b Second binary character ('0' or '1').
+ * @param carry The carry from the previous operation ('0' or '1').
+ * @return The sum as a binary character ('0' or '1').
+ */
+ public char sum(char a, char b, char carry) {
+ int count = 0;
+ if (a == '1') {
+ count++;
+ }
+ if (b == '1') {
+ count++;
+ }
+ if (carry == '1') {
+ count++;
+ }
+ return count % 2 == 0 ? '0' : '1';
+ }
+ /**
+ * Computes the carry for the next higher bit from two binary characters and a carry.
+ * @param a First binary character ('0' or '1').
+ * @param b Second binary character ('0' or '1').
+ * @param carry The carry from the previous operation ('0' or '1').
+ * @return The carry for the next bit ('0' or '1').
+ */
+ public char carry(char a, char b, char carry) {
+ int count = 0;
+ if (a == '1') {
+ count++;
+ }
+ if (b == '1') {
+ count++;
+ }
+ if (carry == '1') {
+ count++;
+ }
+ return count >= 2 ? '1' : '0';
+ }
+ /**
+ * Adds two binary strings and returns their sum as a binary string.
+ * @param a First binary string.
+ * @param b Second binary string.
+ * @return Binary string representing the sum of the two binary inputs.
+ */
+ public String addBinary(String a, String b) {
+ // Padding the shorter string with leading zeros
+ int maxLength = Math.max(a.length(), b.length());
+ a = String.join("", Collections.nCopies(maxLength - a.length(), "0")) + a;
+ b = String.join("", Collections.nCopies(maxLength - b.length(), "0")) + b;
+ StringBuilder result = new StringBuilder();
+ char carry = '0';
+ // Iterating over the binary strings from the least significant to the most significant bit
+ for (int i = maxLength - 1; i >= 0; i--) {
+ char sum = sum(a.charAt(i), b.charAt(i), carry);
+ carry = carry(a.charAt(i), b.charAt(i), carry);
+ result.append(sum);
+ }
+ // If there's a remaining carry, append it
+ if (carry == '1') {
+ result.append('1');
+ }
+ // Reverse the result as we constructed it from the least significant bit
+ return result.reverse().toString();
+ }
+}
diff --git a/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java b/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java
new file mode 100644
index 000000000000..893ca02ed8a3
--- /dev/null
+++ b/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java
@@ -0,0 +1,96 @@
+package com.thealgorithms.greedyalgorithms;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class BinaryAdditionTest {
+
+ BinaryAddition binaryAddition = new BinaryAddition();
+
+ @Test
+ public void testEqualLengthNoCarry() {
+ String a = "1010";
+ String b = "1101";
+ String expected = "10111";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testEqualLengthWithCarry() {
+ String a = "1111";
+ String b = "1111";
+ String expected = "11110";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testDifferentLengths() {
+ String a = "101";
+ String b = "11";
+ String expected = "1000";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testAllZeros() {
+ String a = "0";
+ String b = "0";
+ String expected = "0";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testAllOnes() {
+ String a = "1111";
+ String b = "1111";
+ String expected = "11110";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testOneZeroString() {
+ String a = "0";
+ String b = "10101";
+ String expected = "10101";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+
+ // Test the other way around
+ a = "10101";
+ b = "0";
+ expected = "10101";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testLargeBinaryNumbers() {
+ String a = "101010101010101010101010101010";
+ String b = "110110110110110110110110110110";
+ String expected = "1100001100001100001100001100000";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testOneMuchLonger() {
+ String a = "1";
+ String b = "11111111";
+ String expected = "100000000";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testEmptyStrings() {
+ String a = "";
+ String b = "";
+ String expected = ""; // Adding two empty strings should return 0
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+
+ @Test
+ public void testAlternatingBits() {
+ String a = "10101010";
+ String b = "01010101";
+ String expected = "11111111";
+ assertEquals(expected, binaryAddition.addBinary(a, b));
+ }
+}
From 93cfa86a97c9c13a7387f4e39388261eb1f90282 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 19:53:21 +0530
Subject: [PATCH 092/457] Add tests for `A5KeyStreamGenerator.java`, improve
documentation (#5595)
---
DIRECTORY.md | 1 +
.../ciphers/a5/A5KeyStreamGenerator.java | 69 ++++++++++++++++++-
.../ciphers/a5/A5KeyStreamGeneratorTest.java | 60 ++++++++++++++++
3 files changed, 128 insertions(+), 2 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 2b4dc078c704..6e9524120e9c 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -633,6 +633,7 @@
* [SingleBitOperationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java)
* ciphers
* a5
+ * [A5KeyStreamGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java)
* [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java)
* [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java)
* [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java)
diff --git a/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java b/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java
index 0b17a685bc57..ee837ef4241a 100644
--- a/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java
+++ b/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java
@@ -2,15 +2,39 @@
import java.util.BitSet;
-// TODO: raise exceptions for improper use
+/**
+ * The A5KeyStreamGenerator class is responsible for generating key streams
+ * for the A5/1 encryption algorithm using a combination of Linear Feedback Shift Registers (LFSRs).
+ *
+ *
+ * This class extends the CompositeLFSR and initializes a set of LFSRs with
+ * a session key and a frame counter to produce a pseudo-random key stream.
+ *
+ *
+ *
+ * Note: Proper exception handling for invalid usage is to be implemented.
+ *
+ */
public class A5KeyStreamGenerator extends CompositeLFSR {
private BitSet initialFrameCounter;
private BitSet frameCounter;
private BitSet sessionKey;
private static final int INITIAL_CLOCKING_CYCLES = 100;
- private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something
+ private static final int KEY_STREAM_LENGTH = 228;
+ /**
+ * Initializes the A5KeyStreamGenerator with the specified session key and frame counter.
+ *
+ *
+ * This method sets up the internal state of the LFSRs using the provided
+ * session key and frame counter. It creates three LFSRs with specific
+ * configurations and initializes them.
+ *
+ *
+ * @param sessionKey a BitSet representing the session key used for key stream generation.
+ * @param frameCounter a BitSet representing the frame counter that influences the key stream.
+ */
@Override
public void initialize(BitSet sessionKey, BitSet frameCounter) {
this.sessionKey = sessionKey;
@@ -26,10 +50,26 @@ public void initialize(BitSet sessionKey, BitSet frameCounter) {
registers.forEach(lfsr -> lfsr.initialize(sessionKey, frameCounter));
}
+ /**
+ * Re-initializes the key stream generator with the original session key
+ * and frame counter. This method restores the generator to its initial
+ * state.
+ */
public void reInitialize() {
this.initialize(sessionKey, initialFrameCounter);
}
+ /**
+ * Generates the next key stream of bits.
+ *
+ *
+ * This method performs an initial set of clocking cycles and then retrieves
+ * a key stream of the specified length. After generation, it re-initializes
+ * the internal registers.
+ *
+ *
+ * @return a BitSet containing the generated key stream bits.
+ */
public BitSet getNextKeyStream() {
for (int cycle = 1; cycle <= INITIAL_CLOCKING_CYCLES; ++cycle) {
this.clock();
@@ -45,12 +85,37 @@ public BitSet getNextKeyStream() {
return result;
}
+ /**
+ * Re-initializes the registers for the LFSRs.
+ *
+ *
+ * This method increments the frame counter and re-initializes each LFSR
+ * with the current session key and frame counter.
+ *
+ */
private void reInitializeRegisters() {
incrementFrameCounter();
registers.forEach(lfsr -> lfsr.initialize(sessionKey, frameCounter));
}
+ /**
+ * Increments the current frame counter.
+ *
+ *
+ * This method uses a utility function to increment the frame counter,
+ * which influences the key stream generation process.
+ *
+ */
private void incrementFrameCounter() {
Utils.increment(frameCounter, FRAME_COUNTER_LENGTH);
}
+
+ /**
+ * Retrieves the current frame counter.
+ *
+ * @return a BitSet representing the current state of the frame counter.
+ */
+ public BitSet getFrameCounter() {
+ return frameCounter;
+ }
}
diff --git a/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java b/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java
new file mode 100644
index 000000000000..bb18d4500fc0
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java
@@ -0,0 +1,60 @@
+package com.thealgorithms.ciphers.a5;
+
+import static com.thealgorithms.ciphers.a5.A5KeyStreamGenerator.FRAME_COUNTER_LENGTH;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.BitSet;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class A5KeyStreamGeneratorTest {
+
+ private A5KeyStreamGenerator keyStreamGenerator;
+ private BitSet sessionKey;
+ private BitSet frameCounter;
+
+ @BeforeEach
+ void setUp() {
+ keyStreamGenerator = new A5KeyStreamGenerator();
+
+ // Initialize session key and frame counter for testing
+ sessionKey = BitSet.valueOf(new long[] {0b1010101010101010L}); // Example 16-bit key
+ frameCounter = BitSet.valueOf(new long[] {0b0000000000000001L}); // Example 16-bit frame counter
+ keyStreamGenerator.initialize(sessionKey, frameCounter);
+ }
+
+ @Test
+ void testInitialization() {
+ // Verify that the internal state is set up correctly
+ assertNotNull(keyStreamGenerator, "KeyStreamGenerator should be initialized");
+ }
+
+ @Test
+ void testIncrementFrameCounter() {
+ // Generate key stream to increment the frame counter
+ keyStreamGenerator.getNextKeyStream();
+
+ // The frame counter should have been incremented
+ BitSet incrementedFrameCounter = keyStreamGenerator.getFrameCounter();
+
+ // Check if the incremented frame counter is expected
+ BitSet expectedFrameCounter = (BitSet) frameCounter.clone();
+ Utils.increment(expectedFrameCounter, FRAME_COUNTER_LENGTH);
+
+ assertEquals(expectedFrameCounter, incrementedFrameCounter, "Frame counter should be incremented after generating key stream");
+ }
+
+ @Test
+ void testGetNextKeyStreamProducesDifferentOutputs() {
+ // Generate a key stream
+ BitSet firstKeyStream = keyStreamGenerator.getNextKeyStream();
+
+ // Generate another key stream
+ BitSet secondKeyStream = keyStreamGenerator.getNextKeyStream();
+
+ // Assert that consecutive key streams are different
+ assertNotEquals(firstKeyStream, secondKeyStream, "Consecutive key streams should be different");
+ }
+}
From 26e8ead4edebff4aabc90495ee3f4123d01f2325 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 19:58:01 +0530
Subject: [PATCH 093/457] Add tests for `A5Cipher.java`, improve class &
function documentation (#5594)
---
DIRECTORY.md | 1 +
.../thealgorithms/ciphers/a5/A5Cipher.java | 39 ++++++++++++--
.../ciphers/a5/A5CipherTest.java | 51 +++++++++++++++++++
3 files changed, 88 insertions(+), 3 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 6e9524120e9c..90b26ae98797 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -633,6 +633,7 @@
* [SingleBitOperationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java)
* ciphers
* a5
+ * [A5CipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java)
* [A5KeyStreamGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java)
* [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java)
* [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java)
diff --git a/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java b/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java
index b7d36db5c809..cc2e9105229a 100644
--- a/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java
@@ -2,17 +2,43 @@
import java.util.BitSet;
-// https://en.wikipedia.org/wiki/A5/1
+/**
+ * The A5Cipher class implements the A5/1 stream cipher, which is a widely used
+ * encryption algorithm, particularly in mobile communications.
+ *
+ * This implementation uses a key stream generator to produce a stream of bits
+ * that are XORed with the plaintext bits to produce the ciphertext.
+ *
+ *
+ * For more details about the A5/1 algorithm, refer to
+ * Wikipedia.
+ *
+ */
public class A5Cipher {
private final A5KeyStreamGenerator keyStreamGenerator;
- private static final int KEY_STREAM_LENGTH = 228; // 28.5 bytes so we need to pad bytes or something
-
+ private static final int KEY_STREAM_LENGTH = 228; // Length of the key stream in bits (28.5 bytes)
+
+ /**
+ * Constructs an A5Cipher instance with the specified session key and frame counter.
+ *
+ * @param sessionKey a BitSet representing the session key used for encryption.
+ * @param frameCounter a BitSet representing the frame counter that helps in key stream generation.
+ */
public A5Cipher(BitSet sessionKey, BitSet frameCounter) {
keyStreamGenerator = new A5KeyStreamGenerator();
keyStreamGenerator.initialize(sessionKey, frameCounter);
}
+ /**
+ * Encrypts the given plaintext bits using the A5/1 cipher algorithm.
+ *
+ * This method generates a key stream and XORs it with the provided plaintext
+ * bits to produce the ciphertext.
+ *
+ * @param plainTextBits a BitSet representing the plaintext bits to be encrypted.
+ * @return a BitSet containing the encrypted ciphertext bits.
+ */
public BitSet encrypt(BitSet plainTextBits) {
// create a copy
var result = new BitSet(KEY_STREAM_LENGTH);
@@ -24,6 +50,13 @@ public BitSet encrypt(BitSet plainTextBits) {
return result;
}
+ /**
+ * Resets the internal counter of the key stream generator.
+ *
+ * This method can be called to re-initialize the state of the key stream
+ * generator, allowing for new key streams to be generated for subsequent
+ * encryptions.
+ */
public void resetCounter() {
keyStreamGenerator.reInitialize();
}
diff --git a/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java b/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java
new file mode 100644
index 000000000000..aa725b644a86
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java
@@ -0,0 +1,51 @@
+package com.thealgorithms.ciphers.a5;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.util.BitSet;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class A5CipherTest {
+
+ private A5Cipher a5Cipher;
+ private BitSet sessionKey;
+ private BitSet frameCounter;
+
+ @BeforeEach
+ void setUp() {
+ // Initialize the session key and frame counter
+ sessionKey = BitSet.valueOf(new long[] {0b1010101010101010L});
+ frameCounter = BitSet.valueOf(new long[] {0b0000000000000001L});
+ a5Cipher = new A5Cipher(sessionKey, frameCounter);
+ }
+
+ @Test
+ void testEncryptWithValidInput() {
+ BitSet plainText = BitSet.valueOf(new long[] {0b1100110011001100L}); // Example plaintext
+ BitSet encrypted = a5Cipher.encrypt(plainText);
+
+ // The expected result depends on the key stream generated.
+ // In a real test, you would replace this with the actual expected result.
+ // For now, we will just assert that the encrypted result is not equal to the plaintext.
+ assertNotEquals(plainText, encrypted, "Encrypted output should not equal plaintext");
+ }
+
+ @Test
+ void testEncryptAllOnesInput() {
+ BitSet plainText = BitSet.valueOf(new long[] {0b1111111111111111L}); // All ones
+ BitSet encrypted = a5Cipher.encrypt(plainText);
+
+ // Similar to testEncryptWithValidInput, ensure that output isn't the same as input
+ assertNotEquals(plainText, encrypted, "Encrypted output should not equal plaintext of all ones");
+ }
+
+ @Test
+ void testEncryptAllZerosInput() {
+ BitSet plainText = new BitSet(); // All zeros
+ BitSet encrypted = a5Cipher.encrypt(plainText);
+
+ // Check that the encrypted output is not the same
+ assertNotEquals(plainText, encrypted, "Encrypted output should not equal plaintext of all zeros");
+ }
+}
From 99d7f80a61cfdb01b4eb8d93d2df4f1862d55bf0 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 20:30:57 +0530
Subject: [PATCH 094/457] Add Junit tests for `AESEncryption.java` (#5597)
---
DIRECTORY.md | 1 +
.../ciphers/AESEncryptionTest.java | 62 +++++++++++++++++++
2 files changed, 63 insertions(+)
create mode 100644 src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 90b26ae98797..4b98173a4e85 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -636,6 +636,7 @@
* [A5CipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java)
* [A5KeyStreamGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java)
* [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java)
+ * [AESEncryptionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java)
* [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java)
* [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java)
* [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java)
diff --git a/src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java b/src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java
new file mode 100644
index 000000000000..2f0831e35064
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java
@@ -0,0 +1,62 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.crypto.SecretKey;
+import org.junit.jupiter.api.Test;
+
+public class AESEncryptionTest {
+
+ @Test
+ public void testGetSecretEncryptionKey() throws Exception {
+ SecretKey key = AESEncryption.getSecretEncryptionKey();
+ assertNotNull(key, "Secret key should not be null");
+ assertEquals(128, key.getEncoded().length * 8, "Key size should be 128 bits");
+ }
+
+ @Test
+ public void testEncryptText() throws Exception {
+ String plainText = "Hello World";
+ SecretKey secKey = AESEncryption.getSecretEncryptionKey();
+ byte[] cipherText = AESEncryption.encryptText(plainText, secKey);
+
+ assertNotNull(cipherText, "Ciphertext should not be null");
+ assertTrue(cipherText.length > 0, "Ciphertext should not be empty");
+ }
+
+ @Test
+ public void testDecryptText() throws Exception {
+ String plainText = "Hello World";
+ SecretKey secKey = AESEncryption.getSecretEncryptionKey();
+ byte[] cipherText = AESEncryption.encryptText(plainText, secKey);
+
+ // Decrypt the ciphertext
+ String decryptedText = AESEncryption.decryptText(cipherText, secKey);
+
+ assertNotNull(decryptedText, "Decrypted text should not be null");
+ assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text");
+ }
+
+ @Test
+ public void testEncryptDecrypt() throws Exception {
+ String plainText = "Hello AES!";
+ SecretKey secKey = AESEncryption.getSecretEncryptionKey();
+
+ // Encrypt the plaintext
+ byte[] cipherText = AESEncryption.encryptText(plainText, secKey);
+
+ // Decrypt the ciphertext
+ String decryptedText = AESEncryption.decryptText(cipherText, secKey);
+
+ assertEquals(plainText, decryptedText, "Decrypted text should match the original plain text");
+ }
+
+ @Test
+ public void testBytesToHex() {
+ byte[] bytes = new byte[] {0, 1, 15, 16, (byte) 255}; // Test with diverse byte values
+ String hex = AESEncryption.bytesToHex(bytes);
+ assertEquals("00010F10FF", hex, "Hex representation should match the expected value");
+ }
+}
From f80850b2447365d518ad0f8117360700a82dda59 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 20:38:55 +0530
Subject: [PATCH 095/457] Add Junit tests for `AffineCipher.java`, improve
documentation (#5598)
---
DIRECTORY.md | 1 +
.../thealgorithms/ciphers/AffineCipher.java | 51 +++++++++++++++----
.../ciphers/AffineCipherTest.java | 39 ++++++++++++++
3 files changed, 80 insertions(+), 11 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 4b98173a4e85..d8b1045b5c35 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -637,6 +637,7 @@
* [A5KeyStreamGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java)
* [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java)
* [AESEncryptionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java)
+ * [AffineCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java)
* [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java)
* [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java)
* [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java)
diff --git a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
index bcf3a5b0167b..636323b63646 100644
--- a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java
@@ -1,5 +1,23 @@
package com.thealgorithms.ciphers;
+/**
+ * The AffineCipher class implements the Affine cipher, a type of monoalphabetic substitution cipher.
+ * It encrypts and decrypts messages using a linear transformation defined by the formula:
+ *
+ * E(x) = (a * x + b) mod m
+ * D(y) = a^-1 * (y - b) mod m
+ *
+ * where:
+ * - E(x) is the encrypted character,
+ * - D(y) is the decrypted character,
+ * - a is the multiplicative key (must be coprime to m),
+ * - b is the additive key,
+ * - x is the index of the plaintext character,
+ * - y is the index of the ciphertext character,
+ * - m is the size of the alphabet (26 for the English alphabet).
+ *
+ * The class provides methods for encrypting and decrypting messages, as well as a main method to demonstrate its usage.
+ */
final class AffineCipher {
private AffineCipher() {
}
@@ -8,16 +26,22 @@ private AffineCipher() {
static int a = 17;
static int b = 20;
+ /**
+ * Encrypts a message using the Affine cipher.
+ *
+ * @param msg the plaintext message as a character array
+ * @return the encrypted ciphertext
+ */
static String encryptMessage(char[] msg) {
- /// Cipher Text initially empty
+ // Cipher Text initially empty
String cipher = "";
for (int i = 0; i < msg.length; i++) {
// Avoid space to be encrypted
- /* applying encryption formula ( a x + b ) mod m
+ /* applying encryption formula ( a * x + b ) mod m
{here x is msg[i] and m is 26} and added 'A' to
- bring it in range of ascii alphabet[ 65-90 | A-Z ] */
+ bring it in the range of ASCII alphabet [65-90 | A-Z] */
if (msg[i] != ' ') {
- cipher = cipher + (char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A');
+ cipher += (char) ((((a * (msg[i] - 'A')) + b) % 26) + 'A');
} else { // else simply append space character
cipher += msg[i];
}
@@ -25,28 +49,33 @@ static String encryptMessage(char[] msg) {
return cipher;
}
+ /**
+ * Decrypts a ciphertext using the Affine cipher.
+ *
+ * @param cipher the ciphertext to decrypt
+ * @return the decrypted plaintext message
+ */
static String decryptCipher(String cipher) {
String msg = "";
int aInv = 0;
- int flag = 0;
+ int flag;
- // Find a^-1 (the multiplicative inverse of a
- // in the group of integers modulo m.)
+ // Find a^-1 (the multiplicative inverse of a in the group of integers modulo m.)
for (int i = 0; i < 26; i++) {
flag = (a * i) % 26;
- // Check if (a*i)%26 == 1,
+ // Check if (a * i) % 26 == 1,
// then i will be the multiplicative inverse of a
if (flag == 1) {
aInv = i;
}
}
for (int i = 0; i < cipher.length(); i++) {
- /*Applying decryption formula a^-1 ( x - b ) mod m
+ /* Applying decryption formula a^-1 * (x - b) mod m
{here x is cipher[i] and m is 26} and added 'A'
- to bring it in range of ASCII alphabet[ 65-90 | A-Z ] */
+ to bring it in the range of ASCII alphabet [65-90 | A-Z] */
if (cipher.charAt(i) != ' ') {
- msg = msg + (char) (((aInv * ((cipher.charAt(i) + 'A' - b)) % 26)) + 'A');
+ msg += (char) (((aInv * ((cipher.charAt(i) - 'A') - b + 26)) % 26) + 'A');
} else { // else simply append space character
msg += cipher.charAt(i);
}
diff --git a/src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java b/src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java
new file mode 100644
index 000000000000..b1a2ce593a8e
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java
@@ -0,0 +1,39 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class AffineCipherTest {
+
+ @Test
+ public void testEncryptMessage() {
+ String plaintext = "AFFINE CIPHER";
+ char[] msg = plaintext.toCharArray();
+ String expectedCiphertext = "UBBAHK CAPJKX"; // Expected ciphertext after encryption
+
+ String actualCiphertext = AffineCipher.encryptMessage(msg);
+ assertEquals(expectedCiphertext, actualCiphertext, "The encryption result should match the expected ciphertext.");
+ }
+
+ @Test
+ public void testEncryptDecrypt() {
+ String plaintext = "HELLO WORLD";
+ char[] msg = plaintext.toCharArray();
+
+ String ciphertext = AffineCipher.encryptMessage(msg);
+ String decryptedText = AffineCipher.decryptCipher(ciphertext);
+
+ assertEquals(plaintext, decryptedText, "Decrypted text should match the original plaintext.");
+ }
+
+ @Test
+ public void testSpacesHandledInEncryption() {
+ String plaintext = "HELLO WORLD";
+ char[] msg = plaintext.toCharArray();
+ String expectedCiphertext = "JKZZY EYXZT";
+
+ String actualCiphertext = AffineCipher.encryptMessage(msg);
+ assertEquals(expectedCiphertext, actualCiphertext, "The encryption should handle spaces correctly.");
+ }
+}
From 25dc55e4ae784af32f2aa53e95439106a3befd05 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 20:41:56 +0530
Subject: [PATCH 096/457] Add Junit tests for
`ColumnarTranspositionCipher.java` (#5599)
---
DIRECTORY.md | 1 +
.../ciphers/ColumnarTranspositionCipher.java | 20 --------
.../ColumnarTranspositionCipherTest.java | 47 +++++++++++++++++++
3 files changed, 48 insertions(+), 20 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index d8b1045b5c35..02a24678b83f 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -641,6 +641,7 @@
* [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java)
* [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java)
* [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java)
+ * [ColumnarTranspositionCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java)
* [DESTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DESTest.java)
* [HillCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java)
* [PlayfairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java)
diff --git a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
index e59cfb12d816..d7e64a12ebfd 100644
--- a/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
+++ b/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java
@@ -184,24 +184,4 @@ private static void abecedariumBuilder(int value) {
}
abecedarium = t.toString();
}
-
- private static void showTable() {
- for (Object[] table1 : table) {
- for (Object item : table1) {
- System.out.print(item + " ");
- }
- System.out.println();
- }
- }
-
- public static void main(String[] args) {
- String keywordForExample = "asd215";
- String wordBeingEncrypted = "This is a test of the Columnar Transposition Cipher";
- System.out.println("### Example of Columnar Transposition Cipher ###\n");
- System.out.println("Word being encryped ->>> " + wordBeingEncrypted);
- System.out.println("Word encrypted ->>> " + ColumnarTranspositionCipher.encrpyter(wordBeingEncrypted, keywordForExample));
- System.out.println("Word decryped ->>> " + ColumnarTranspositionCipher.decrypter());
- System.out.println("\n### Encrypted Table ###");
- showTable();
- }
}
diff --git a/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java b/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java
new file mode 100644
index 000000000000..0d306a6623db
--- /dev/null
+++ b/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java
@@ -0,0 +1,47 @@
+package com.thealgorithms.ciphers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ColumnarTranspositionCipherTest {
+ private String keyword;
+ private String plaintext;
+
+ @BeforeEach
+ public void setUp() {
+ keyword = "keyword";
+ plaintext = "This is a test message for Columnar Transposition Cipher";
+ }
+
+ @Test
+ public void testEncryption() {
+ String encryptedText = ColumnarTranspositionCipher.encrpyter(plaintext, keyword);
+ assertNotNull(encryptedText, "The encrypted text should not be null.");
+ assertFalse(encryptedText.isEmpty(), "The encrypted text should not be empty.");
+ // Check if the encrypted text is different from the plaintext
+ assertNotEquals(plaintext, encryptedText, "The encrypted text should be different from the plaintext.");
+ }
+
+ @Test
+ public void testDecryption() {
+ String encryptedText = ColumnarTranspositionCipher.encrpyter(plaintext, keyword);
+ String decryptedText = ColumnarTranspositionCipher.decrypter();
+
+ assertEquals(plaintext.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original plaintext, ignoring spaces.");
+ assertEquals(encryptedText, ColumnarTranspositionCipher.encrpyter(plaintext, keyword), "The encrypted text should be the same when encrypted again.");
+ }
+
+ @Test
+ public void testLongPlainText() {
+ String longText = "This is a significantly longer piece of text to test the encryption and decryption capabilities of the Columnar Transposition Cipher. It should handle long strings gracefully.";
+ String encryptedText = ColumnarTranspositionCipher.encrpyter(longText, keyword);
+ String decryptedText = ColumnarTranspositionCipher.decrypter();
+ assertEquals(longText.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original long plaintext, ignoring spaces.");
+ assertEquals(encryptedText, ColumnarTranspositionCipher.encrpyter(longText, keyword), "The encrypted text should be the same when encrypted again.");
+ }
+}
From d422bf5983aa7c715af17cdf2592ed53316cd2c6 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 20:45:50 +0530
Subject: [PATCH 097/457] Add tests for `AffineConverter.java` (#5602)
---
DIRECTORY.md | 1 +
.../conversions/AffineConverterTest.java | 55 +++++++++++++++++++
2 files changed, 56 insertions(+)
create mode 100644 src/test/java/com/thealgorithms/conversions/AffineConverterTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 02a24678b83f..7e5d0d3de9ba 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -651,6 +651,7 @@
* [VigenereTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/VigenereTest.java)
* [XORCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/XORCipherTest.java)
* conversions
+ * [AffineConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java)
* [AnyBaseToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AnyBaseToDecimalTest.java)
* [BinaryToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java)
* [BinaryToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToHexadecimalTest.java)
diff --git a/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java b/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java
new file mode 100644
index 000000000000..2705955f68f6
--- /dev/null
+++ b/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java
@@ -0,0 +1,55 @@
+package com.thealgorithms.conversions;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class AffineConverterTest {
+
+ private AffineConverter converter;
+
+ @BeforeEach
+ void setUp() {
+ converter = new AffineConverter(2.0, 3.0);
+ }
+
+ @Test
+ void testConstructor() {
+ assertEquals(3.0, converter.convert(0.0), "Expected value when input is 0.0");
+ assertEquals(5.0, converter.convert(1.0), "Expected value when input is 1.0");
+ assertEquals(7.0, converter.convert(2.0), "Expected value when input is 2.0");
+ }
+
+ @Test
+ void testConvert() {
+ assertEquals(3.0, converter.convert(0.0), "Conversion at 0.0 should equal the intercept");
+ assertEquals(7.0, converter.convert(2.0), "2.0 should convert to 7.0");
+ assertEquals(11.0, converter.convert(4.0), "4.0 should convert to 11.0");
+ }
+
+ @Test
+ void testInvert() {
+ AffineConverter inverted = converter.invert();
+ assertEquals(0.0, inverted.convert(3.0), "Inverted converter should return 0.0 for input 3.0");
+ assertEquals(1.0, inverted.convert(5.0), "Inverted converter should return 1.0 for input 5.0");
+ assertEquals(2.0, inverted.convert(7.0), "Inverted converter should return 2.0 for input 7.0");
+ }
+
+ @Test
+ void testInvertWithZeroSlope() {
+ AffineConverter zeroSlopeConverter = new AffineConverter(0.0, 3.0);
+ assertThrows(AssertionError.class, zeroSlopeConverter::invert, "Invert should throw assertion error when slope is zero");
+ }
+
+ @Test
+ void testCompose() {
+ AffineConverter otherConverter = new AffineConverter(1.0, 2.0);
+ AffineConverter composed = converter.compose(otherConverter);
+
+ assertEquals(7.0, composed.convert(0.0), "Expected composed conversion at 0.0");
+ assertEquals(9.0, composed.convert(1.0), "Expected composed conversion at 1.0");
+ assertEquals(11.0, composed.convert(2.0), "Expected composed conversion at 2.0");
+ }
+}
From 62144f61af289cc94cec380d680b8865726c459a Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 21:55:48 +0530
Subject: [PATCH 098/457] Add tests for `AStar.java`, enhance documentation
(#5603)
---
DIRECTORY.md | 1 +
.../datastructures/graphs/AStar.java | 144 ++++++------------
.../datastructures/graphs/AStarTest.java | 46 ++++++
3 files changed, 92 insertions(+), 99 deletions(-)
create mode 100644 src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 7e5d0d3de9ba..87922528abda 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -694,6 +694,7 @@
* dynamicarray
* [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java)
* graphs
+ * [AStarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java)
* [BipartiteGraphDFSTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java)
* [BoruvkaAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java)
* [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java)
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
index 54fb5fba5c1b..460c05e04403 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
@@ -1,25 +1,26 @@
-/*
- Time Complexity = O(E), where E is equal to the number of edges
- */
package com.thealgorithms.datastructures.graphs;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
+/**
+ * AStar class implements the A* pathfinding algorithm to find the shortest path in a graph.
+ * The graph is represented using an adjacency list, and the algorithm uses a heuristic to estimate
+ * the cost to reach the destination node.
+ * Time Complexity = O(E), where E is equal to the number of edges
+ */
public final class AStar {
private AStar() {
}
- private static class Graph {
-
- // Graph's structure can be changed only applying changes to this class.
-
+ /**
+ * Represents a graph using an adjacency list.
+ */
+ static class Graph {
private ArrayList> graph;
- // Initialise ArrayLists in Constructor
Graph(int size) {
this.graph = new ArrayList<>();
for (int i = 0; i < size; i++) {
@@ -31,15 +32,17 @@ private ArrayList getNeighbours(int from) {
return this.graph.get(from);
}
- // Graph is bidirectional, for just one direction remove second instruction of this method.
+ // Add a bidirectional edge to the graph
private void addEdge(Edge edge) {
this.graph.get(edge.getFrom()).add(new Edge(edge.getFrom(), edge.getTo(), edge.getWeight()));
this.graph.get(edge.getTo()).add(new Edge(edge.getTo(), edge.getFrom(), edge.getWeight()));
}
}
+ /**
+ * Represents an edge in the graph with a start node, end node, and weight.
+ */
private static class Edge {
-
private int from;
private int to;
private int weight;
@@ -63,12 +66,13 @@ public int getWeight() {
}
}
- // class to iterate during the algorithm execution, and also used to return the solution.
- private static class PathAndDistance {
-
- private int distance; // distance advanced so far.
- private ArrayList path; // list of visited nodes in this path.
- private int estimated; // heuristic value associated to the last node od the path (current node).
+ /**
+ * Contains information about the path and its total distance.
+ */
+ static class PathAndDistance {
+ private int distance; // total distance from the start node
+ private ArrayList path; // list of nodes in the path
+ private int estimated; // heuristic estimate for reaching the destination
PathAndDistance(int distance, ArrayList path, int estimated) {
this.distance = distance;
@@ -87,112 +91,54 @@ public ArrayList getPath() {
public int getEstimated() {
return estimated;
}
-
- private void printSolution() {
- if (this.path != null) {
- System.out.println("Optimal path: " + this.path + ", distance: " + this.distance);
- } else {
- System.out.println("There is no path available to connect the points");
- }
- }
}
- private static void initializeGraph(Graph graph, ArrayList data) {
+ // Initializes the graph with edges defined in the input data
+ static void initializeGraph(Graph graph, ArrayList data) {
for (int i = 0; i < data.size(); i += 4) {
graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2)));
}
- /*
- .x. node
- (y) cost
- - or | or / bidirectional connection
-
- ( 98)- .7. -(86)- .4.
- |
- ( 85)- .17. -(142)- .18. -(92)- .8. -(87)- .11.
- |
- . 1. -------------------- (160)
- | \ |
- (211) \ .6.
- | \ |
- . 5. (101)-.13. -(138) (115)
- | | | /
- ( 99) ( 97) | /
- | | | /
- .12. -(151)- .15. -(80)- .14. | /
- | | | | /
- ( 71) (140) (146)- .2. -(120)
- | | |
- .19. -( 75)- . 0. .10. -(75)- .3.
- | |
- (118) ( 70)
- | |
- .16. -(111)- .9.
- */
- }
-
- public static void main(String[] args) {
- // heuristic function optimistic values
- int[] heuristic = {
- 366,
- 0,
- 160,
- 242,
- 161,
- 178,
- 77,
- 151,
- 226,
- 244,
- 241,
- 234,
- 380,
- 98,
- 193,
- 253,
- 329,
- 80,
- 199,
- 374,
- };
-
- Graph graph = new Graph(20);
- ArrayList graphData = new ArrayList<>(Arrays.asList(0, 19, 75, null, 0, 15, 140, null, 0, 16, 118, null, 19, 12, 71, null, 12, 15, 151, null, 16, 9, 111, null, 9, 10, 70, null, 10, 3, 75, null, 3, 2, 120, null, 2, 14, 146, null, 2, 13, 138, null, 2, 6, 115, null, 15, 14, 80, null,
- 15, 5, 99, null, 14, 13, 97, null, 5, 1, 211, null, 13, 1, 101, null, 6, 1, 160, null, 1, 17, 85, null, 17, 7, 98, null, 7, 4, 86, null, 17, 18, 142, null, 18, 8, 92, null, 8, 11, 87));
- initializeGraph(graph, graphData);
-
- PathAndDistance solution = aStar(3, 1, graph, heuristic);
- solution.printSolution();
}
+ /**
+ * Implements the A* pathfinding algorithm to find the shortest path from a start node to a destination node.
+ *
+ * @param from the starting node
+ * @param to the destination node
+ * @param graph the graph representation of the problem
+ * @param heuristic the heuristic estimates for each node
+ * @return a PathAndDistance object containing the shortest path and its distance
+ */
public static PathAndDistance aStar(int from, int to, Graph graph, int[] heuristic) {
- // nodes are prioritised by the less value of the current distance of their paths, and the
- // estimated value
- // given by the heuristic function to reach the destination point from the current point.
+ // PriorityQueue to explore nodes based on their distance and estimated cost to reach the destination
PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated())));
- // dummy data to start the algorithm from the beginning point
- queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), 0));
+ // Start with the initial node
+ queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), heuristic[from]));
boolean solutionFound = false;
PathAndDistance currentData = new PathAndDistance(-1, null, -1);
+
while (!queue.isEmpty() && !solutionFound) {
- currentData = queue.poll(); // first in the queue, best node so keep exploring.
- int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node.
+ currentData = queue.poll(); // get the best node from the queue
+ int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node
+
+ // Check if the destination has been reached
if (currentPosition == to) {
solutionFound = true;
} else {
for (Edge edge : graph.getNeighbours(currentPosition)) {
- if (!currentData.getPath().contains(edge.getTo())) { // Avoid Cycles
+ // Avoid cycles by checking if the next node is already in the path
+ if (!currentData.getPath().contains(edge.getTo())) {
ArrayList updatedPath = new ArrayList<>(currentData.getPath());
- updatedPath.add(edge.getTo()); // Add the new node to the path, update the distance,
- // and the heuristic function value associated to that path.
+ updatedPath.add(edge.getTo());
+
+ // Update the distance and heuristic for the new path
queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()]));
}
}
}
}
return (solutionFound) ? currentData : new PathAndDistance(-1, null, -1);
- // Out of while loop, if there is a solution, the current Data stores the optimal path, and
- // its distance
}
}
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java
new file mode 100644
index 000000000000..dce5a6ed4b69
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.datastructures.graphs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class AStarTest {
+
+ private AStar.Graph graph;
+ private int[] heuristic;
+
+ @BeforeEach
+ public void setUp() {
+ // Initialize graph and heuristic values for testing
+ graph = new AStar.Graph(5);
+ ArrayList graphData = new ArrayList<>(Arrays.asList(0, 1, 1, null, 0, 2, 2, null, 1, 3, 1, null, 2, 3, 1, null, 3, 4, 1, null));
+ AStar.initializeGraph(graph, graphData);
+
+ heuristic = new int[] {5, 4, 3, 2, 0}; // Heuristic values for each node
+ }
+
+ @Test
+ public void testAStarFindsPath() {
+ AStar.PathAndDistance result = AStar.aStar(0, 4, graph, heuristic);
+ assertEquals(3, result.getDistance(), "Expected distance from 0 to 4 is 3");
+ assertEquals(Arrays.asList(0, 1, 3, 4), result.getPath(), "Expected path from 0 to 4");
+ }
+
+ @Test
+ public void testAStarPathNotFound() {
+ AStar.PathAndDistance result = AStar.aStar(0, 5, graph, heuristic); // Node 5 does not exist
+ assertEquals(-1, result.getDistance(), "Expected distance when path not found is -1");
+ assertNull(result.getPath(), "Expected path should be null when no path exists");
+ }
+
+ @Test
+ public void testAStarSameNode() {
+ AStar.PathAndDistance result = AStar.aStar(0, 0, graph, heuristic);
+ assertEquals(0, result.getDistance(), "Expected distance from 0 to 0 is 0");
+ assertEquals(Arrays.asList(0), result.getPath(), "Expected path should only contain the start node");
+ }
+}
From 676d451aa605a9442cdda85badbd0877816d6cf7 Mon Sep 17 00:00:00 2001
From: Hardik Pawar <97388607+Hardvan@users.noreply.github.com>
Date: Mon, 7 Oct 2024 22:17:29 +0530
Subject: [PATCH 099/457] Add class documentation, improve comments in
`MazeRecursion.java` (#5576)
---
.../backtracking/MazeRecursion.java | 205 ++++++++----------
.../backtracking/MazeRecursionTest.java | 34 ++-
2 files changed, 103 insertions(+), 136 deletions(-)
diff --git a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
index f7eae01e449a..8247172e7ee0 100644
--- a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
+++ b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
@@ -1,152 +1,125 @@
package com.thealgorithms.backtracking;
+/**
+ * This class contains methods to solve a maze using recursive backtracking.
+ * The maze is represented as a 2D array where walls, paths, and visited/dead
+ * ends are marked with different integers.
+ *
+ * The goal is to find a path from a starting position to the target position
+ * (map[6][5]) while navigating through the maze.
+ */
public final class MazeRecursion {
+
private MazeRecursion() {
}
- public static void mazeRecursion() {
- // First create a 2 dimensions array to mimic a maze map
- int[][] map = new int[8][7];
- int[][] map2 = new int[8][7];
-
- // We use 1 to indicate wall
- // Set the ceiling and floor to 1
- for (int i = 0; i < 7; i++) {
- map[0][i] = 1;
- map[7][i] = 1;
- }
-
- // Then we set the left and right wall to 1
- for (int i = 0; i < 8; i++) {
- map[i][0] = 1;
- map[i][6] = 1;
- }
-
- // Now we have created a maze with its wall initialized
-
- // Here we set the obstacle
- map[3][1] = 1;
- map[3][2] = 1;
-
- // Print the current map
- System.out.println("The condition of the map: ");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map[i][j] + " ");
- }
- System.out.println();
- }
-
- // clone another map for setWay2 method
- for (int i = 0; i < map.length; i++) {
- System.arraycopy(map[i], 0, map2[i], 0, map[i].length);
- }
-
- // By using recursive backtracking to let your ball(target) find its way in the
- // maze
- // The first parameter is the map
- // Second parameter is x coordinate of your target
- // Third parameter is the y coordinate of your target
- setWay(map, 1, 1);
- setWay2(map2, 1, 1);
-
- // Print out the new map1, with the ball footprint
- System.out.println("After the ball goes through the map1,show the current map1 condition");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map[i][j] + " ");
- }
- System.out.println();
+ /**
+ * This method solves the maze using the "down -> right -> up -> left"
+ * movement strategy.
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @return The solved maze with paths marked, or null if no solution exists.
+ */
+ public static int[][] solveMazeUsingFirstStrategy(int[][] map) {
+ if (setWay(map, 1, 1)) {
+ return map;
}
+ return null;
+ }
- // Print out the new map2, with the ball footprint
- System.out.println("After the ball goes through the map2,show the current map2 condition");
- for (int i = 0; i < 8; i++) {
- for (int j = 0; j < 7; j++) {
- System.out.print(map2[i][j] + " ");
- }
- System.out.println();
+ /**
+ * This method solves the maze using the "up -> right -> down -> left"
+ * movement strategy.
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @return The solved maze with paths marked, or null if no solution exists.
+ */
+ public static int[][] solveMazeUsingSecondStrategy(int[][] map) {
+ if (setWay2(map, 1, 1)) {
+ return map;
}
+ return null;
}
/**
- * Using recursive path finding to help the ball find its way in the maze
- * Description:
- * 1. map (means the maze)
- * 2. i, j (means the initial coordinate of the ball in the maze)
- * 3. if the ball can reach the end of maze, that is position of map[6][5],
- * means the we have found a path for the ball
- * 4. Additional Information: 0 in the map[i][j] means the ball has not gone
- * through this position, 1 means the wall, 2 means the path is feasible, 3
- * means the ball has gone through the path but this path is dead end
- * 5. We will need strategy for the ball to pass through the maze for example:
- * Down -> Right -> Up -> Left, if the path doesn't work, then backtrack
+ * Attempts to find a path through the maze using a "down -> right -> up -> left"
+ * movement strategy. The path is marked with '2' for valid paths and '3' for dead ends.
*
- * @author OngLipWei
- * @version Jun 23, 2021 11:36:14 AM
- * @param map The maze
- * @param i x coordinate of your ball(target)
- * @param j y coordinate of your ball(target)
- * @return If we did find a path for the ball,return true,else false
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @param i The current x-coordinate of the ball (row index)
+ * @param j The current y-coordinate of the ball (column index)
+ * @return True if a path is found to (6,5), otherwise false
*/
- public static boolean setWay(int[][] map, int i, int j) {
- if (map[6][5] == 2) { // means the ball find its path, ending condition
+ private static boolean setWay(int[][] map, int i, int j) {
+ if (map[6][5] == 2) {
return true;
}
- if (map[i][j] == 0) { // if the ball haven't gone through this point
- // then the ball follows the move strategy : down -> right -> up -> left
- map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2
- // first。
- if (setWay(map, i + 1, j)) { // go down
+
+ // If the current position is unvisited (0), explore it
+ if (map[i][j] == 0) {
+ // Mark the current position as '2'
+ map[i][j] = 2;
+
+ // Move down
+ if (setWay(map, i + 1, j)) {
return true;
- } else if (setWay(map, i, j + 1)) { // go right
+ }
+ // Move right
+ else if (setWay(map, i, j + 1)) {
return true;
- } else if (setWay(map, i - 1, j)) { // go up
+ }
+ // Move up
+ else if (setWay(map, i - 1, j)) {
return true;
- } else if (setWay(map, i, j - 1)) { // go left
+ }
+ // Move left
+ else if (setWay(map, i, j - 1)) {
return true;
- } else {
- // means that the current point is the dead end, the ball cannot proceed, set
- // the current point to 3 and return false, the backtracking will start, it will
- // go to the previous step and check for feasible path again
- map[i][j] = 3;
- return false;
}
- } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the
- // ball cannot hit the wall, cannot go to the path that has gone though before,
- // and cannot head to deadened.
+
+ map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
}
+ return false;
}
- // Here is another move strategy for the ball: up->right->down->left
- public static boolean setWay2(int[][] map, int i, int j) {
- if (map[6][5] == 2) { // means the ball find its path, ending condition
+ /**
+ * Attempts to find a path through the maze using an alternative movement
+ * strategy "up -> right -> down -> left".
+ *
+ * @param map The 2D array representing the maze (walls, paths, etc.)
+ * @param i The current x-coordinate of the ball (row index)
+ * @param j The current y-coordinate of the ball (column index)
+ * @return True if a path is found to (6,5), otherwise false
+ */
+ private static boolean setWay2(int[][] map, int i, int j) {
+ if (map[6][5] == 2) {
return true;
}
- if (map[i][j] == 0) { // if the ball haven't gone through this point
- // then the ball follows the move strategy : up->right->down->left
- map[i][j] = 2; // we assume that this path is feasible first, set the current point to 2
- // first。
- if (setWay2(map, i - 1, j)) { // go up
+
+ if (map[i][j] == 0) {
+ map[i][j] = 2;
+
+ // Move up
+ if (setWay2(map, i - 1, j)) {
return true;
- } else if (setWay2(map, i, j + 1)) { // go right
+ }
+ // Move right
+ else if (setWay2(map, i, j + 1)) {
return true;
- } else if (setWay2(map, i + 1, j)) { // go down
+ }
+ // Move down
+ else if (setWay2(map, i + 1, j)) {
return true;
- } else if (setWay2(map, i, j - 1)) { // go left
+ }
+ // Move left
+ else if (setWay2(map, i, j - 1)) {
return true;
- } else {
- // means that the current point is the dead end, the ball cannot proceed, set
- // the current point to 3 and return false, the backtracking will start, it will
- // go to the previous step and check for feasible path again
- map[i][j] = 3;
- return false;
}
- } else { // if the map[i][j] != 0 , it will probably be 1,2,3, return false because the
- // ball cannot hit the wall, cannot go to the path that has gone through before,
- // and cannot head to deadend.
+
+ map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
}
+ return false;
}
}
diff --git a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java
index edaca14af067..b8e77fb38bad 100644
--- a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java
+++ b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java
@@ -11,41 +11,35 @@
public class MazeRecursionTest {
@Test
- public void testMaze() {
- // First create a 2 dimensions array to mimic a maze map
+ public void testSolveMazeUsingFirstAndSecondStrategy() {
int[][] map = new int[8][7];
int[][] map2 = new int[8][7];
- // We use 1 to indicate wall
+ // We use 1 to indicate walls
// Set the ceiling and floor to 1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
-
- // Then we set the left and right wall to 1
+ // Set the left and right wall to 1
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
-
- // Now we have created a maze with its wall initialized
-
- // Here we set the obstacle
+ // Set obstacles
map[3][1] = 1;
map[3][2] = 1;
- // clone another map for setWay2 method
+ // Clone the original map for the second pathfinding strategy
for (int i = 0; i < map.length; i++) {
- for (int j = 0; j < map[i].length; j++) {
- map2[i][j] = map[i][j];
- }
+ System.arraycopy(map[i], 0, map2[i], 0, map[i].length);
}
- MazeRecursion.setWay(map, 1, 1);
- MazeRecursion.setWay2(map2, 1, 1);
-
- int[][] expectedMap = new int[][] {
+ // Solve the maze using the first strategy
+ int[][] solvedMap1 = MazeRecursion.solveMazeUsingFirstStrategy(map);
+ // Solve the maze using the second strategy
+ int[][] solvedMap2 = MazeRecursion.solveMazeUsingSecondStrategy(map2);
+ int[][] expectedMap1 = new int[][] {
{1, 1, 1, 1, 1, 1, 1},
{1, 2, 0, 0, 0, 0, 1},
{1, 2, 2, 2, 0, 0, 1},
@@ -55,7 +49,6 @@ public void testMaze() {
{1, 0, 0, 2, 2, 2, 1},
{1, 1, 1, 1, 1, 1, 1},
};
-
int[][] expectedMap2 = new int[][] {
{1, 1, 1, 1, 1, 1, 1},
{1, 2, 2, 2, 2, 2, 1},
@@ -67,7 +60,8 @@ public void testMaze() {
{1, 1, 1, 1, 1, 1, 1},
};
- assertArrayEquals(map, expectedMap);
- assertArrayEquals(map2, expectedMap2);
+ // Assert the results
+ assertArrayEquals(expectedMap1, solvedMap1);
+ assertArrayEquals(expectedMap2, solvedMap2);
}
}
From 732f7c8458669fca84f9800c0d0864a5634eca6a Mon Sep 17 00:00:00 2001
From: Prayas Kumar <71717433+prayas7102@users.noreply.github.com>
Date: Mon, 7 Oct 2024 23:00:46 +0530
Subject: [PATCH 100/457] Add BM25 Inverted Index Search Algorithm (#5615)
---
.../searches/BM25InvertedIndex.java | 220 ++++++++++++++++++
.../searches/BM25InvertedIndexTest.java | 93 ++++++++
2 files changed, 313 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java
create mode 100644 src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java
diff --git a/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java b/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java
new file mode 100644
index 000000000000..1cfd2bbad8e4
--- /dev/null
+++ b/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java
@@ -0,0 +1,220 @@
+package com.thealgorithms.searches;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Inverted Index implementation with BM25 Scoring for movie search.
+ * This class supports adding movie documents and searching for terms
+ * within those documents using the BM25 algorithm.
+ * @author Prayas Kumar (https://github.com/prayas7102)
+ */
+
+class Movie {
+ int docId; // Unique identifier for the movie
+ String name; // Movie name
+ double imdbRating; // IMDb rating of the movie
+ int releaseYear; // Year the movie was released
+ String content; // Full text content (could be the description or script)
+
+ /**
+ * Constructor for the Movie class.
+ * @param docId Unique identifier for the movie.
+ * @param name Name of the movie.
+ * @param imdbRating IMDb rating of the movie.
+ * @param releaseYear Release year of the movie.
+ * @param content Content or description of the movie.
+ */
+ Movie(int docId, String name, double imdbRating, int releaseYear, String content) {
+ this.docId = docId;
+ this.name = name;
+ this.imdbRating = imdbRating;
+ this.releaseYear = releaseYear;
+ this.content = content;
+ }
+
+ /**
+ * Get all the words from the movie's name and content.
+ * Converts the name and content to lowercase and splits on non-word characters.
+ * @return Array of words from the movie name and content.
+ */
+ public String[] getWords() {
+ return (name + " " + content).toLowerCase().split("\\W+");
+ }
+
+ @Override
+ public String toString() {
+ return "Movie{"
+ + "docId=" + docId + ", name='" + name + '\'' + ", imdbRating=" + imdbRating + ", releaseYear=" + releaseYear + '}';
+ }
+}
+
+class SearchResult {
+ int docId; // Unique identifier of the movie document
+ double relevanceScore; // Relevance score based on the BM25 algorithm
+
+ /**
+ * Constructor for SearchResult class.
+ * @param docId Document ID (movie) for this search result.
+ * @param relevanceScore The relevance score based on BM25 scoring.
+ */
+ SearchResult(int docId, double relevanceScore) {
+ this.docId = docId;
+ this.relevanceScore = relevanceScore;
+ }
+
+ public int getDocId() {
+ return docId;
+ }
+
+ @Override
+ public String toString() {
+ return "SearchResult{"
+ + "docId=" + docId + ", relevanceScore=" + relevanceScore + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SearchResult that = (SearchResult) o;
+ return docId == that.docId && Double.compare(that.relevanceScore, relevanceScore) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(docId, relevanceScore);
+ }
+
+ public double getRelevanceScore() {
+ return this.relevanceScore;
+ }
+}
+
+public final class BM25InvertedIndex {
+ private Map> index; // Inverted index mapping terms to document id and frequency
+ private Map movies; // Mapping of movie document IDs to Movie objects
+ private int totalDocuments; // Total number of movies/documents
+ private double avgDocumentLength; // Average length of documents (number of words)
+ private static final double K = 1.5; // BM25 tuning parameter, controls term frequency saturation
+ private static final double B = 0.75; // BM25 tuning parameter, controls length normalization
+
+ /**
+ * Constructor for BM25InvertedIndex.
+ * Initializes the inverted index and movie storage.
+ */
+ BM25InvertedIndex() {
+ index = new HashMap<>();
+ movies = new HashMap<>();
+ totalDocuments = 0;
+ avgDocumentLength = 0.0;
+ }
+
+ /**
+ * Add a movie to the index.
+ * @param docId Unique identifier for the movie.
+ * @param name Name of the movie.
+ * @param imdbRating IMDb rating of the movie.
+ * @param releaseYear Release year of the movie.
+ * @param content Content or description of the movie.
+ */
+ public void addMovie(int docId, String name, double imdbRating, int releaseYear, String content) {
+ Movie movie = new Movie(docId, name, imdbRating, releaseYear, content);
+ movies.put(docId, movie);
+ totalDocuments++;
+
+ // Get words (terms) from the movie's name and content
+ String[] terms = movie.getWords();
+ int docLength = terms.length;
+
+ // Update the average document length
+ avgDocumentLength = (avgDocumentLength * (totalDocuments - 1) + docLength) / totalDocuments;
+
+ // Update the inverted index
+ for (String term : terms) {
+ // Create a new entry if the term is not yet in the index
+ index.putIfAbsent(term, new HashMap<>());
+
+ // Get the list of documents containing the term
+ Map docList = index.get(term);
+ if (docList == null) {
+ docList = new HashMap<>();
+ index.put(term, docList); // Ensure docList is added to the index
+ }
+ // Increment the term frequency in this document
+ docList.put(docId, docList.getOrDefault(docId, 0) + 1);
+ }
+ }
+
+ public int getMoviesLength() {
+ return movies.size();
+ }
+
+ /**
+ * Search for documents containing a term using BM25 scoring.
+ * @param term The search term.
+ * @return A list of search results sorted by relevance score.
+ */
+ public List search(String term) {
+ term = term.toLowerCase(); // Normalize search term
+ if (!index.containsKey(term)) {
+ return new ArrayList<>(); // Return empty list if term not found
+ }
+
+ Map termDocs = index.get(term); // Documents containing the term
+ List results = new ArrayList<>();
+
+ // Compute IDF for the search term
+ double idf = computeIDF(termDocs.size());
+
+ // Calculate relevance scores for all documents containing the term
+ for (Map.Entry entry : termDocs.entrySet()) {
+ int docId = entry.getKey();
+ int termFrequency = entry.getValue();
+ Movie movie = movies.get(docId);
+ if (movie == null) {
+ continue; // Skip this document if movie doesn't exist
+ }
+ double docLength = movie.getWords().length;
+
+ // Compute BM25 relevance score
+ double score = computeBM25Score(termFrequency, docLength, idf);
+ results.add(new SearchResult(docId, score));
+ }
+
+ // Sort the results by relevance score in descending order
+ results.sort((r1, r2) -> Double.compare(r2.relevanceScore, r1.relevanceScore));
+ return results;
+ }
+
+ /**
+ * Compute the BM25 score for a given term and document.
+ * @param termFrequency The frequency of the term in the document.
+ * @param docLength The length of the document.
+ * @param idf The inverse document frequency of the term.
+ * @return The BM25 relevance score for the term in the document.
+ */
+ private double computeBM25Score(int termFrequency, double docLength, double idf) {
+ double numerator = termFrequency * (K + 1);
+ double denominator = termFrequency + K * (1 - B + B * (docLength / avgDocumentLength));
+ return idf * (numerator / denominator);
+ }
+
+ /**
+ * Compute the inverse document frequency (IDF) of a term.
+ * The IDF measures the importance of a term across the entire document set.
+ * @param docFrequency The number of documents that contain the term.
+ * @return The inverse document frequency (IDF) value.
+ */
+ private double computeIDF(int docFrequency) {
+ // Total number of documents in the index
+ return Math.log((totalDocuments - docFrequency + 0.5) / (docFrequency + 0.5));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java b/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java
new file mode 100644
index 000000000000..8595e0a00683
--- /dev/null
+++ b/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java
@@ -0,0 +1,93 @@
+package com.thealgorithms.searches;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test Cases for Inverted Index with BM25
+ * @author Prayas Kumar (https://github.com/prayas7102)
+ */
+
+class BM25InvertedIndexTest {
+
+ private static BM25InvertedIndex index;
+
+ @BeforeAll
+ static void setUp() {
+ index = new BM25InvertedIndex();
+ index.addMovie(1, "The Shawshank Redemption", 9.3, 1994, "Hope is a good thing. Maybe the best of things. And no good thing ever dies.");
+ index.addMovie(2, "The Godfather", 9.2, 1972, "I'm gonna make him an offer he can't refuse.");
+ index.addMovie(3, "The Dark Knight", 9.0, 2008, "You either die a hero or live long enough to see yourself become the villain.");
+ index.addMovie(4, "Pulp Fiction", 8.9, 1994, "You know what they call a Quarter Pounder with Cheese in Paris? They call it a Royale with Cheese.");
+ index.addMovie(5, "Good Will Hunting", 8.3, 1997, "Will Hunting is a genius and he has a good heart. The best of his abilities is yet to be explored.");
+ index.addMovie(6, "It's a Wonderful Life", 8.6, 1946, "Each man's life touches so many other lives. If he wasn't around, it would leave an awfully good hole.");
+ index.addMovie(7, "The Pursuit of Happyness", 8.0, 2006, "It was the pursuit of a better life, and a good opportunity to change things for the better.");
+ index.addMovie(8, "A Few Good Men", 7.7, 1992, "You can't handle the truth! This movie has a lot of good moments and intense drama.");
+ }
+
+ @Test
+ void testAddMovie() {
+ // Check that the index contains the correct number of movies
+ int moviesLength = index.getMoviesLength();
+ assertEquals(8, moviesLength);
+ }
+
+ @Test
+ void testSearchForTermFound() {
+ int expected = 1;
+ List result = index.search("hope");
+ int actual = result.getFirst().getDocId();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ void testSearchRanking() {
+ // Perform search for the term "good"
+ List results = index.search("good");
+ assertFalse(results.isEmpty());
+
+ // Validate the ranking based on the provided relevance scores
+ assertEquals(6, results.get(0).getDocId()); // It's a Wonderful Life should be ranked 1st
+ assertEquals(7, results.get(1).getDocId()); // The Pursuit of Happyness should be ranked 2nd
+ assertEquals(5, results.get(2).getDocId()); // Good Will Hunting should be ranked 3rd
+ assertEquals(8, results.get(3).getDocId()); // A Few Good Men should be ranked 4th
+ assertEquals(1, results.get(4).getDocId()); // The Shawshank Redemption should be ranked 5th
+
+ // Ensure the relevance scores are in descending order
+ for (int i = 0; i < results.size() - 1; i++) {
+ assertTrue(results.get(i).getRelevanceScore() > results.get(i + 1).getRelevanceScore());
+ }
+ }
+
+ @Test
+ void testSearchForTermNotFound() {
+ List results = index.search("nonexistent");
+ assertTrue(results.isEmpty());
+ }
+
+ @Test
+ void testSearchForCommonTerm() {
+ List results = index.search("the");
+ assertFalse(results.isEmpty());
+ assertTrue(results.size() > 1);
+ }
+
+ @Test
+ void testBM25ScoreCalculation() {
+ List results = index.search("cheese");
+ assertEquals(1, results.size());
+ assertEquals(4, results.getFirst().docId); // Pulp Fiction should have the highest score
+ }
+
+ @Test
+ void testCaseInsensitivity() {
+ List resultsLowerCase = index.search("hope");
+ List resultsUpperCase = index.search("HOPE");
+ assertEquals(resultsLowerCase, resultsUpperCase);
+ }
+}
From 4a5bf39f8e196efd9772d3708217dd5554d14a1d Mon Sep 17 00:00:00 2001
From: Taranjeet Singh Kalsi
Date: Tue, 8 Oct 2024 00:18:02 +0530
Subject: [PATCH 101/457] Add another method to check valid parentheses in
ValidParentheses.java (#5616)
---
.../strings/ValidParentheses.java | 18 ++++++++++++++++++
.../strings/ValidParenthesesTest.java | 3 +++
2 files changed, 21 insertions(+)
diff --git a/src/main/java/com/thealgorithms/strings/ValidParentheses.java b/src/main/java/com/thealgorithms/strings/ValidParentheses.java
index f4f3761b0495..629fee495d84 100644
--- a/src/main/java/com/thealgorithms/strings/ValidParentheses.java
+++ b/src/main/java/com/thealgorithms/strings/ValidParentheses.java
@@ -38,4 +38,22 @@ public static boolean isValid(String s) {
}
return head == 0;
}
+ public static boolean isValidParentheses(String s) {
+ int i = -1;
+ char[] stack = new char[s.length()];
+ String openBrackets = "({[";
+ String closeBrackets = ")}]";
+ for (char ch : s.toCharArray()) {
+ if (openBrackets.indexOf(ch) != -1) {
+ stack[++i] = ch;
+ } else {
+ if (i >= 0 && openBrackets.indexOf(stack[i]) == closeBrackets.indexOf(ch)) {
+ i--;
+ } else {
+ return false;
+ }
+ }
+ }
+ return i == -1;
+ }
}
diff --git a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
index 22deb4b14d3c..2b6884c91c8f 100644
--- a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
+++ b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java
@@ -10,15 +10,18 @@ public class ValidParenthesesTest {
@Test
void testOne() {
assertTrue(ValidParentheses.isValid("()"));
+ assertTrue(ValidParentheses.isValidParentheses("()"));
}
@Test
void testTwo() {
assertTrue(ValidParentheses.isValid("()[]{}"));
+ assertTrue(ValidParentheses.isValidParentheses("()[]{}"));
}
@Test
void testThree() {
assertFalse(ValidParentheses.isValid("(]"));
+ assertFalse(ValidParentheses.isValidParentheses("(]"));
}
}
From 6868bf8ba01041a50fdb37b953ff462d68526879 Mon Sep 17 00:00:00 2001
From: Nandini Pandey <120239212+Nandini-Pandey@users.noreply.github.com>
Date: Tue, 8 Oct 2024 00:28:17 +0530
Subject: [PATCH 102/457] Add palindrome singly linkedlist optimised approach
(#5617)
---
.../misc/PalindromeSinglyLinkedList.java | 43 +++++++++++
.../misc/PalindromeSinglyLinkedListTest.java | 72 +++++++++++++++++++
2 files changed, 115 insertions(+)
diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
index 07286b39f2e4..8af8a9b030e1 100644
--- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
+++ b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java
@@ -30,4 +30,47 @@ public static boolean isPalindrome(final SinglyLinkedList linkedList) {
return true;
}
+
+ // Optimised approach with O(n) time complexity and O(1) space complexity
+
+ public static boolean isPalindromeOptimised(Node head) {
+ if (head == null || head.next == null) {
+ return true;
+ }
+ Node slow = head;
+ Node fast = head;
+ while (fast != null && fast.next != null) {
+ slow = slow.next;
+ fast = fast.next.next;
+ }
+ Node midNode = slow;
+
+ Node prevNode = null;
+ Node currNode = midNode;
+ Node nextNode;
+ while (currNode != null) {
+ nextNode = currNode.next;
+ currNode.next = prevNode;
+ prevNode = currNode;
+ currNode = nextNode;
+ }
+ Node left = head;
+ Node right = prevNode;
+ while (left != null && right != null) {
+ if (left.val != right.val) {
+ return false;
+ }
+ right = right.next;
+ left = left.next;
+ }
+ return true;
+ }
+ static class Node {
+ int val;
+ Node next;
+ Node(int val) {
+ this.val = val;
+ this.next = null;
+ }
+ }
}
diff --git a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java b/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java
index ae0d6ae0674d..0f0577d39094 100644
--- a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java
+++ b/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java
@@ -7,6 +7,8 @@
import org.junit.jupiter.api.Test;
public class PalindromeSinglyLinkedListTest {
+
+ // Stack-based tests
@Test
public void testWithEmptyList() {
assertTrue(PalindromeSinglyLinkedList.isPalindrome(new SinglyLinkedList()));
@@ -67,4 +69,74 @@ public void testWithListWithEvenLengthNegative() {
exampleList.insert(20);
assertFalse(PalindromeSinglyLinkedList.isPalindrome(exampleList));
}
+
+ // Optimized approach tests
+ @Test
+ public void testOptimisedWithEmptyList() {
+ assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(null));
+ }
+
+ @Test
+ public void testOptimisedWithSingleElement() {
+ PalindromeSinglyLinkedList.Node node = new PalindromeSinglyLinkedList.Node(100);
+ assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node));
+ }
+
+ @Test
+ public void testOptimisedWithOddLengthPositive() {
+ PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(1);
+ PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2);
+ PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(1);
+ node1.next = node2;
+ node2.next = node3;
+ assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
+ }
+
+ @Test
+ public void testOptimisedWithOddLengthPositive2() {
+ PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(3);
+ PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2);
+ PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(1);
+ PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(2);
+ PalindromeSinglyLinkedList.Node node5 = new PalindromeSinglyLinkedList.Node(3);
+ node1.next = node2;
+ node2.next = node3;
+ node3.next = node4;
+ node4.next = node5;
+ assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
+ }
+
+ @Test
+ public void testOptimisedWithEvenLengthPositive() {
+ PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(10);
+ PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(20);
+ PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(20);
+ PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(10);
+ node1.next = node2;
+ node2.next = node3;
+ node3.next = node4;
+ assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
+ }
+
+ @Test
+ public void testOptimisedWithOddLengthNegative() {
+ PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(1);
+ PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2);
+ PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(2);
+ node1.next = node2;
+ node2.next = node3;
+ assertFalse(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
+ }
+
+ @Test
+ public void testOptimisedWithEvenLengthNegative() {
+ PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(10);
+ PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(20);
+ PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(20);
+ PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(20);
+ node1.next = node2;
+ node2.next = node3;
+ node3.next = node4;
+ assertFalse(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
+ }
}
From 5dcf6c0f29d6c850709fd27672efc27f337bf8b5 Mon Sep 17 00:00:00 2001
From: Sailok Chinta
Date: Tue, 8 Oct 2024 02:41:55 +0530
Subject: [PATCH 103/457] Enhance Trie data structure with added methods and
tests (#5538)
---
DIRECTORY.md | 2 +-
.../trees/{TrieImp.java => Trie.java} | 141 +++++++++++++-----
.../trees/{TrieImpTest.java => TrieTest.java} | 37 +++--
3 files changed, 129 insertions(+), 51 deletions(-)
rename src/main/java/com/thealgorithms/datastructures/trees/{TrieImp.java => Trie.java} (50%)
rename src/test/java/com/thealgorithms/datastructures/trees/{TrieImpTest.java => TrieTest.java} (69%)
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 87922528abda..ad337f35fc8f 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -207,7 +207,7 @@
* [SplayTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SplayTree.java)
* [Treap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/Treap.java)
* [TreeRandomNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TreeRandomNode.java)
- * [TrieImp](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java)
+ * [Trie](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/Trie.java)
* [VerticalOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversal.java)
* [ZigzagTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/ZigzagTraversal.java)
* devutils
diff --git a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java b/src/main/java/com/thealgorithms/datastructures/trees/Trie.java
similarity index 50%
rename from src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java
rename to src/main/java/com/thealgorithms/datastructures/trees/Trie.java
index a43a454146cb..02f28d4d83ad 100644
--- a/src/main/java/com/thealgorithms/datastructures/trees/TrieImp.java
+++ b/src/main/java/com/thealgorithms/datastructures/trees/Trie.java
@@ -1,5 +1,28 @@
package com.thealgorithms.datastructures.trees;
+import java.util.HashMap;
+
+/**
+ * Represents a Trie Node that stores a character and pointers to its children.
+ * Each node has a hashmap which can point to all possible characters.
+ * Each node also has a boolean value to indicate if it is the end of a word.
+ */
+class TrieNode {
+ char value;
+ HashMap child;
+ boolean end;
+
+ /**
+ * Constructor to initialize a TrieNode with an empty hashmap
+ * and set end to false.
+ */
+ TrieNode(char value) {
+ this.value = value;
+ this.child = new HashMap<>();
+ this.end = false;
+ }
+}
+
/**
* Trie Data structure implementation without any libraries.
*
@@ -14,27 +37,11 @@
* possible character.
*
* @author Dheeraj Kumar Barnwal
+ * @author Sailok Chinta
*/
-public class TrieImp {
- /**
- * Represents a Trie Node that stores a character and pointers to its children.
- * Each node has an array of 26 children (one for each letter from 'a' to 'z').
- */
- public class TrieNode {
-
- TrieNode[] child;
- boolean end;
-
- /**
- * Constructor to initialize a TrieNode with an empty child array
- * and set end to false.
- */
- public TrieNode() {
- child = new TrieNode[26];
- end = false;
- }
- }
+public class Trie {
+ private static final char ROOT_NODE_VALUE = '*';
private final TrieNode root;
@@ -42,8 +49,8 @@ public TrieNode() {
* Constructor to initialize the Trie.
* The root node is created but doesn't represent any character.
*/
- public TrieImp() {
- root = new TrieNode();
+ public Trie() {
+ root = new TrieNode(ROOT_NODE_VALUE);
}
/**
@@ -57,13 +64,15 @@ public TrieImp() {
public void insert(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
- TrieNode node = currentNode.child[word.charAt(i) - 'a'];
+ TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null);
+
if (node == null) {
- node = new TrieNode();
- currentNode.child[word.charAt(i) - 'a'] = node;
+ node = new TrieNode(word.charAt(i));
+ currentNode.child.put(word.charAt(i), node);
}
currentNode = node;
}
+
currentNode.end = true;
}
@@ -80,13 +89,14 @@ public void insert(String word) {
public boolean search(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
- char ch = word.charAt(i);
- TrieNode node = currentNode.child[ch - 'a'];
+ TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null);
+
if (node == null) {
return false;
}
currentNode = node;
}
+
return currentNode.end;
}
@@ -104,40 +114,89 @@ public boolean search(String word) {
public boolean delete(String word) {
TrieNode currentNode = root;
for (int i = 0; i < word.length(); i++) {
- char ch = word.charAt(i);
- TrieNode node = currentNode.child[ch - 'a'];
+ TrieNode node = currentNode.child.getOrDefault(word.charAt(i), null);
if (node == null) {
return false;
}
+
currentNode = node;
}
+
if (currentNode.end) {
currentNode.end = false;
return true;
}
+
return false;
}
/**
- * Helper method to print a string to the console.
+ * Counts the number of words in the trie
+ *
+ * The method traverses the Trie and counts the number of words.
*
- * @param print The string to be printed.
+ * @return count of words
*/
- public static void sop(String print) {
- System.out.println(print);
+ public int countWords() {
+ return countWords(root);
+ }
+
+ private int countWords(TrieNode node) {
+ if (node == null) {
+ return 0;
+ }
+
+ int count = 0;
+ if (node.end) {
+ count++;
+ }
+
+ for (TrieNode child : node.child.values()) {
+ count += countWords(child);
+ }
+
+ return count;
}
/**
- * Validates if a given word contains only lowercase alphabetic characters
- * (a-z).
- *
- * The method uses a regular expression to check if the word matches the pattern
- * of only lowercase letters.
+ * Check if the prefix exists in the trie
*
- * @param word The word to be validated.
- * @return true if the word is valid (only a-z), false otherwise.
+ * @param prefix the prefix to be checked in the Trie
+ * @return true / false depending on the prefix if exists in the Trie
*/
- public static boolean isValid(String word) {
- return word.matches("^[a-z]+$");
+ public boolean startsWithPrefix(String prefix) {
+ TrieNode currentNode = root;
+
+ for (int i = 0; i < prefix.length(); i++) {
+ TrieNode node = currentNode.child.getOrDefault(prefix.charAt(i), null);
+ if (node == null) {
+ return false;
+ }
+
+ currentNode = node;
+ }
+
+ return true;
+ }
+
+ /**
+ * Count the number of words starting with the given prefix in the trie
+ *
+ * @param prefix the prefix to be checked in the Trie
+ * @return count of words
+ */
+ public int countWordsWithPrefix(String prefix) {
+ TrieNode currentNode = root;
+
+ for (int i = 0; i < prefix.length(); i++) {
+ TrieNode node = currentNode.child.getOrDefault(prefix.charAt(i), null);
+ if (node == null) {
+ return 0;
+ }
+
+ currentNode = node;
+ }
+
+ return countWords(currentNode);
}
}
diff --git a/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java b/src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java
similarity index 69%
rename from src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java
rename to src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java
index 600fdef0a718..9348118bb343 100644
--- a/src/test/java/com/thealgorithms/datastructures/trees/TrieImpTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java
@@ -1,17 +1,21 @@
package com.thealgorithms.datastructures.trees;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-public class TrieImpTest {
- private TrieImp trie;
+public class TrieTest {
+ private static final List WORDS = List.of("Apple", "App", "app", "APPLE");
+
+ private Trie trie;
@BeforeEach
public void setUp() {
- trie = new TrieImp();
+ trie = new Trie();
}
@Test
@@ -66,11 +70,26 @@ public void testInsertAndSearchPrefix() {
}
@Test
- public void testIsValidWord() {
- assertTrue(TrieImp.isValid("validword"), "Word should be valid (only lowercase letters).");
- assertFalse(TrieImp.isValid("InvalidWord"), "Word should be invalid (contains uppercase letters).");
- assertFalse(TrieImp.isValid("123abc"), "Word should be invalid (contains numbers).");
- assertFalse(TrieImp.isValid("hello!"), "Word should be invalid (contains special characters).");
- assertFalse(TrieImp.isValid(""), "Empty string should be invalid.");
+ public void testCountWords() {
+ Trie trie = createTrie();
+ assertEquals(WORDS.size(), trie.countWords(), "Count words should return the correct number of words.");
+ }
+
+ @Test
+ public void testStartsWithPrefix() {
+ Trie trie = createTrie();
+ assertTrue(trie.startsWithPrefix("App"), "Starts with prefix should return true.");
+ }
+
+ @Test
+ public void testCountWordsWithPrefix() {
+ Trie trie = createTrie();
+ assertEquals(2, trie.countWordsWithPrefix("App"), "Count words with prefix should return 2.");
+ }
+
+ private Trie createTrie() {
+ Trie trie = new Trie();
+ WORDS.forEach(trie::insert);
+ return trie;
}
}
From bd9e324e8c522032cabc0b7c267df7d53ad77b91 Mon Sep 17 00:00:00 2001
From: Sailok Chinta