diff --git a/pmd-exclude.properties b/pmd-exclude.properties index f6ee88196962..58c9895ce84e 100644 --- a/pmd-exclude.properties +++ b/pmd-exclude.properties @@ -88,3 +88,4 @@ com.thealgorithms.strings.HorspoolSearch=UnnecessaryFullyQualifiedName,UselessPa com.thealgorithms.strings.MyAtoi=UselessParentheses com.thealgorithms.strings.Palindrome=UselessParentheses com.thealgorithms.strings.Solution=CollapsibleIfStatements +com.thealgorithms.strings.PatternSearchUsingRabinKarpAlgo.java=UselessParentheses diff --git a/src/main/java/com/thealgorithms/strings/PatternSearchUsingRabinKarpAlgo.java b/src/main/java/com/thealgorithms/strings/PatternSearchUsingRabinKarpAlgo.java new file mode 100644 index 000000000000..afa81a5fdc16 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/PatternSearchUsingRabinKarpAlgo.java @@ -0,0 +1,74 @@ +package com.thealgorithms.strings; +import java.util.ArrayList; +import java.util.List; + +/* + @author Lohit M Kudlannavar (https://github.com/Lohit-pro) + + https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm + The Rabin-Karp algorithm calculates a hash value for the pattern and a hash value for a sliding window + of text in the same length as the pattern. If the hash values match, it checks character by character + to confirm that it's an exact match. +*/ + +public final class PatternSearchUsingRabinKarpAlgo { + + private PatternSearchUsingRabinKarpAlgo() { + } + + // I'm using Rabin-Karp algorithm that uses hashing to find pattern strings in a text. + public static List search(String text, String pattern) { + List result = new ArrayList<>(); + text = text.toLowerCase(); + pattern = pattern.toLowerCase(); + int m = pattern.length(); + int n = text.length(); + int prime = 101; // A prime number to mod hash values + + int patternHash = 0; // Hash value for pattern + int textHash = 0; // Hash value for text window + int h = 1; + + // The value of h would be "pow(d, m-1) % prime" + for (int i = 0; i < m - 1; i++) { + h = h * 256 % prime; + } + + // Calculating the hash value of pattern and first window of text + for (int i = 0; i < m; i++) { + patternHash = (256 * patternHash + pattern.charAt(i)) % prime; + textHash = (256 * textHash + text.charAt(i)) % prime; + } + + // Iterating on pattern by single char again and again.. + for (int i = 0; i <= n - m; i++) { + + if (patternHash == textHash) { + // If the hash values match, then only checking for next char's + int j; + for (j = 0; j < m; j++) { + if (text.charAt(i + j) != pattern.charAt(j)) { + break; + } + } + + // If patternHash == textHash and pattern[0...m-1] == text[i...i+m-1] + if (j == m) { + result.add("Start: " + i + ", End: " + (i + m - 1) + ", Substring: " + text.substring(i, i + m)); + } + } + + // Calculating hash value for next window of text: Remove leading digit, add trailing digit + if (i < n - m) { + textHash = (256 * (textHash - text.charAt(i) * h) + text.charAt(i + m)) % prime; + + // We might get negative value of textHash,so converting it to positive + if (textHash < 0) { + textHash = (textHash + prime); + } + } + } + + return result; + } +} diff --git a/src/test/java/com/thealgorithms/strings/PatternSearchUsingRabinKarpAlgoTest.java b/src/test/java/com/thealgorithms/strings/PatternSearchUsingRabinKarpAlgoTest.java new file mode 100644 index 000000000000..2554915d79f0 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/PatternSearchUsingRabinKarpAlgoTest.java @@ -0,0 +1,72 @@ +package com.thealgorithms.strings; + +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.Test; + +public class PatternSearchUsingRabinKarpAlgoTest { + + @Test + public void testPatternFoundInMiddle() { + String text = "ABCCDDAEFG"; + String pattern = "CDD"; + List result = PatternSearchUsingRabinKarpAlgo.search(text, pattern); + assertFalse(result.isEmpty(), "Pattern should be found"); + assertEquals("Start: 3, End: 5, Substring: cdd", result.get(0)); + } + + @Test + public void testPatternAtStart() { + String text = "ABCCDDAEFG"; + String pattern = "AB"; + List result = PatternSearchUsingRabinKarpAlgo.search(text, pattern); + assertFalse(result.isEmpty(), "Pattern should be found"); + assertEquals("Start: 0, End: 1, Substring: ab", result.get(0)); + } + + @Test + public void testPatternAtEnd() { + String text = "ABCCDDAEFG"; + String pattern = "EFG"; + List result = PatternSearchUsingRabinKarpAlgo.search(text, pattern); + assertFalse(result.isEmpty(), "Pattern should be found"); + assertEquals("Start: 7, End: 9, Substring: efg", result.get(0)); + } + + @Test + public void testPatternNotFound() { + String text = "ABCCDDAEFG"; + String pattern = "XYZ"; + List result = PatternSearchUsingRabinKarpAlgo.search(text, pattern); + assertTrue(result.isEmpty(), "Pattern should not be found"); + } + + @Test + public void testPatternEqualsText() { + String text = "ABCCDDAEFG"; + String pattern = "ABCCDDAEFG"; + List result = PatternSearchUsingRabinKarpAlgo.search(text, pattern); + assertFalse(result.isEmpty(), "Pattern should match entire text"); + assertEquals("Start: 0, End: 9, Substring: abccddaefg", result.get(0)); + } + + @Test + public void testMultipleMatches() { + String text = "AAAAAA"; + String pattern = "AA"; + List result = PatternSearchUsingRabinKarpAlgo.search(text, pattern); + assertEquals(5, result.size(), "Pattern should appear multiple times"); + } + + @Test + public void testCaseInsensitiveSearch() { + String text = "HelloWorld"; + String pattern = "helloworld"; + List result = PatternSearchUsingRabinKarpAlgo.search(text, pattern); + assertFalse(result.isEmpty(), "Pattern should match regardless of case"); + assertEquals("Start: 0, End: 9, Substring: helloworld", result.get(0)); + } +}