diff --git a/README.md b/README.md index 563cfcb7..8e94d0df 100644 --- a/README.md +++ b/README.md @@ -2193,6 +2193,101 @@ public class PalindromCheckSnippet { } } ``` +### Rabin-Karp Substring Search Algorithm + +```java +public class RabinKarpSubstringSearchSnippet { + + private static final int PRIME = 1_000_000_007; // Large prime to reduce collisions + + /** + * Implements the Rabin-Karp algorithm to find the index of the first occurrence + * of a substring in a given text. + * + * @param text The text in which the substring is to be searched. + * @param pattern The substring pattern to search for. + * @return The index of the first occurrence of the pattern in the text, + * or -1 if the pattern is not found. + */ + public static int rabinKarpSearch(String text, String pattern) { + if (pattern == null || pattern.length() == 0) { + return 0; // Trivial case: empty pattern + } + if (text == null || text.length() < pattern.length()) { + return -1; // Pattern cannot be found + } + + int m = pattern.length(); + int n = text.length(); + long patternHash = createHash(pattern, m); + long textHash = createHash(text, m); + + for (int i = 0; i <= n - m; i++) { + if (patternHash == textHash && checkEqual(text, i, i + m - 1, pattern, 0, m - 1)) { + return i; // Match found + } + if (i < n - m) { + textHash = recalculateHash(text, i, i + m, textHash, m); + } + } + return -1; // No match found + } + + /** + * Creates a hash for the given substring. + * + * @param str The string to hash. + * @param end The length of substring considered. + * @return The hash value. + */ + private static long createHash(String str, int end) { + long hash = 0; + for (int i = 0; i < end; i++) { + hash += (long) str.charAt(i) * pow(PRIME, i); + } + return hash; + } + + /** + * Recalculates hash by sliding the window by one character. + */ + private static long recalculateHash(String str, int oldIndex, int newIndex, long oldHash, int patternLen) { + long newHash = oldHash - str.charAt(oldIndex); + newHash /= PRIME; + newHash += (long) str.charAt(newIndex) * pow(PRIME, patternLen - 1); + return newHash; + } + + /** + * Checks if two substrings are equal. + */ + private static boolean checkEqual(String str1, int start1, int end1, String str2, int start2, int end2) { + if (end1 - start1 != end2 - start2) { + return false; + } + while (start1 <= end1 && start2 <= end2) { + if (str1.charAt(start1) != str2.charAt(start2)) { + return false; + } + start1++; + start2++; + } + return true; + } + + /** + * Efficient power function for modular hashing. + */ + private static long pow(int base, int exp) { + long result = 1; + for (int i = 0; i < exp; i++) { + result *= base; + } + return result; + } +} +``` + ### Reverse String diff --git a/src/main/java/string/RabinKarpSubstringSearchSnippet.java b/src/main/java/string/RabinKarpSubstringSearchSnippet.java new file mode 100644 index 00000000..350038cb --- /dev/null +++ b/src/main/java/string/RabinKarpSubstringSearchSnippet.java @@ -0,0 +1,132 @@ +/* + * MIT License + * + * Copyright (c) 2017-2024 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package string; + +/** + * RabinKarpSubstringSearchSnippet. + */ +public class RabinKarpSubstringSearchSnippet { + + private static final int PRIME = 1_000_000_007; // A prime number used as modulus for hashing + + /** + * Implements the Rabin-Karp algorithm to find the index of a substring. + * + * @param text The text in which the substring is to be searched. + * @param pattern The substring pattern to search for. + * @return The index of the first occurrence, or -1 if the pattern is not found. + */ + public static int rabinKarpSearch(String text, String pattern) { + if (pattern == null || pattern.length() == 0) { + return 0; // Trivial case: empty pattern + } + if (text == null || text.length() < pattern.length()) { + return -1; // Pattern cannot be found + } + + int m = pattern.length(); + int n = text.length(); + long patternHash = createHash(pattern, m); + long textHash = createHash(text, m); + + for (int i = 0; i <= n - m; i++) { + if (patternHash == textHash && checkEqual(text, i, i + m - 1, pattern, 0, m - 1)) { + return i; // Match found + } + if (i < n - m) { + textHash = recalculateHash(text, i, i + m, textHash, m); + } + } + return -1; // No match found + } + + /** + * Creates a hash for the given substring. + * + * @param str The string to hash. + * @param end The length of substring considered. + * @return The hash value. + */ + private static long createHash(String str, int end) { + long hash = 0; + for (int i = 0; i < end; i++) { + hash += str.charAt(i) * Math.pow(PRIME, i); + } + return hash; + } + + /** + * Recalculates hash by sliding the window by one character. + * + * @param str The original string. + * @param oldIndex The index of the outgoing character. + * @param newIndex The index of the incoming character. + * @param oldHash The previous hash value. + * @param patternLen The length of the pattern. + * @return The recalculated hash. + */ + private static long recalculateHash( + String str, + int oldIndex, + int newIndex, + long oldHash, + int patternLen) { + long newHash = oldHash - str.charAt(oldIndex); + newHash /= PRIME; + newHash += str.charAt(newIndex) * Math.pow(PRIME, patternLen - 1); + return newHash; + } + + /** + * Checks if two substrings are equal. + * + * @param str1 The first string. + * @param start1 Start index in str1. + * @param end1 End index in str1. + * @param str2 The second string. + * @param start2 Start index in str2. + * @param end2 End index in str2. + * @return True if substrings are equal, otherwise false. + */ + private static boolean checkEqual( + String str1, + int start1, + int end1, + String str2, + int start2, + int end2) { + if (end1 - start1 != end2 - start2) { + return false; + } + while (start1 <= end1 && start2 <= end2) { + if (str1.charAt(start1) != str2.charAt(start2)) { + return false; + } + start1++; + start2++; + } + return true; + } +} diff --git a/src/test/java/string/RabinKarpSubstringSearchSnippetTest.java b/src/test/java/string/RabinKarpSubstringSearchSnippetTest.java new file mode 100644 index 00000000..f8ea2571 --- /dev/null +++ b/src/test/java/string/RabinKarpSubstringSearchSnippetTest.java @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2017-2024 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package string; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Tests for 30 Seconds of Java code library. + * + */ +class RabinKarpSubstringSearchSnippetTest { + + /** + * Tests for {@link RabinKarpSubstringSearchSnippet#rabinKarpSearch(String, String)}. + */ + @Test + void testRabinKarpSearch() { + // Test cases for Rabin-Karp substring search + assertEquals(6, RabinKarpSubstringSearchSnippet.rabinKarpSearch("abxabcabcaby", "abcaby")); + assertEquals(7, RabinKarpSubstringSearchSnippet.rabinKarpSearch("subash pandey", "pandey")); + assertEquals(-1, RabinKarpSubstringSearchSnippet.rabinKarpSearch("abcd", "e")); + assertEquals(0, RabinKarpSubstringSearchSnippet.rabinKarpSearch("aaaaa", "a")); + assertEquals(2, RabinKarpSubstringSearchSnippet.rabinKarpSearch("abcdabcd", "cdab")); + } +}