From 1745d19f09f636657c45e1f341e9f8d10357a687 Mon Sep 17 00:00:00 2001 From: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:04:06 +0200 Subject: [PATCH] refactor: unified duplicate Anagram classes into a single implementation (#6290) --- .../dynamicprogramming/UniquePaths.java | 9 +- .../dynamicprogramming/WildcardMatching.java | 5 +- .../thealgorithms/maths/DudeneyNumber.java | 4 +- .../scheduling/RRScheduling.java | 5 +- .../com/thealgorithms/strings/Anagrams.java | 35 +++--- .../thealgorithms/strings/CheckAnagrams.java | 110 ------------------ .../thealgorithms/strings/AnagramsTest.java | 13 ++- ...rSameTest.java => CharactersSameTest.java} | 0 .../strings/CheckAnagramsTest.java | 69 ----------- 9 files changed, 38 insertions(+), 212 deletions(-) delete mode 100644 src/main/java/com/thealgorithms/strings/CheckAnagrams.java rename src/test/java/com/thealgorithms/strings/{CharacterSameTest.java => CharactersSameTest.java} (100%) delete mode 100644 src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java b/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java index 80b553f27..22ad8a7dd 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java @@ -1,3 +1,7 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.Arrays; + /** * Author: Siddhant Swarup Mallick * Github: https://github.com/siddhant2002 @@ -12,11 +16,6 @@ * This program calculates the number of unique paths possible for a robot to reach the bottom-right corner * of an m x n grid using dynamic programming. */ - -package com.thealgorithms.dynamicprogramming; - -import java.util.Arrays; - public final class UniquePaths { private UniquePaths() { diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java b/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java index 8e8bf3cc6..8658ea30a 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java @@ -1,3 +1,5 @@ +package com.thealgorithms.dynamicprogramming; + /** * * Author: Janmesh Singh @@ -11,9 +13,6 @@ * Use DP to return True if the pattern matches the entire text and False otherwise * */ - -package com.thealgorithms.dynamicprogramming; - public final class WildcardMatching { private WildcardMatching() { } diff --git a/src/main/java/com/thealgorithms/maths/DudeneyNumber.java b/src/main/java/com/thealgorithms/maths/DudeneyNumber.java index acf1e55d4..37f28e188 100644 --- a/src/main/java/com/thealgorithms/maths/DudeneyNumber.java +++ b/src/main/java/com/thealgorithms/maths/DudeneyNumber.java @@ -1,11 +1,11 @@ +package com.thealgorithms.maths; + /** * A number is said to be Dudeney if the sum of the digits, is the cube root of the entered number. * Example- Let the number be 512, its sum of digits is 5+1+2=8. The cube root of 512 is also 8. * Since, the sum of the digits is equal to the cube root of the entered number; * it is a Dudeney Number. */ -package com.thealgorithms.maths; - public final class DudeneyNumber { private DudeneyNumber() { } diff --git a/src/main/java/com/thealgorithms/scheduling/RRScheduling.java b/src/main/java/com/thealgorithms/scheduling/RRScheduling.java index 110c97416..05efe1d59 100644 --- a/src/main/java/com/thealgorithms/scheduling/RRScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/RRScheduling.java @@ -1,7 +1,3 @@ -/** - * @author Md Asif Joardar - */ - package com.thealgorithms.scheduling; import com.thealgorithms.devutils.entities.ProcessDetails; @@ -11,6 +7,7 @@ import java.util.List; import java.util.Queue; /** + * @author Md Asif Joardar * The Round-robin scheduling algorithm is a kind of preemptive First come, First Serve CPU * Scheduling algorithm. This can be understood here - * https://www.scaler.com/topics/round-robin-scheduling-in-os/ diff --git a/src/main/java/com/thealgorithms/strings/Anagrams.java b/src/main/java/com/thealgorithms/strings/Anagrams.java index 4b24979e2..5b97af075 100644 --- a/src/main/java/com/thealgorithms/strings/Anagrams.java +++ b/src/main/java/com/thealgorithms/strings/Anagrams.java @@ -23,7 +23,9 @@ public final class Anagrams { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach1(String s, String t) { + public static boolean areAnagramsBySorting(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } @@ -43,17 +45,18 @@ public final class Anagrams { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach2(String s, String t) { - if (s.length() != t.length()) { - return false; + public static boolean areAnagramsByCountingChars(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); + int[] dict = new int[128]; + for (char ch : s.toCharArray()) { + dict[ch]++; } - int[] charCount = new int[26]; - for (int i = 0; i < s.length(); i++) { - charCount[s.charAt(i) - 'a']++; - charCount[t.charAt(i) - 'a']--; + for (char ch : t.toCharArray()) { + dict[ch]--; } - for (int count : charCount) { - if (count != 0) { + for (int e : dict) { + if (e != 0) { return false; } } @@ -70,7 +73,9 @@ public final class Anagrams { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach3(String s, String t) { + public static boolean areAnagramsByCountingCharsSingleArray(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } @@ -96,7 +101,9 @@ public final class Anagrams { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach4(String s, String t) { + public static boolean areAnagramsUsingHashMap(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } @@ -123,7 +130,9 @@ public final class Anagrams { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach5(String s, String t) { + public static boolean areAnagramsBySingleFreqArray(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } diff --git a/src/main/java/com/thealgorithms/strings/CheckAnagrams.java b/src/main/java/com/thealgorithms/strings/CheckAnagrams.java deleted file mode 100644 index 7bf7cd9a7..000000000 --- a/src/main/java/com/thealgorithms/strings/CheckAnagrams.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.thealgorithms.strings; - -import java.util.HashMap; -import java.util.Map; - -/** - * Two strings are anagrams if they are made of the same letters arranged - * differently (ignoring the case). - */ -public final class CheckAnagrams { - private CheckAnagrams() { - } - /** - * Check if two strings are anagrams or not - * - * @param s1 the first string - * @param s2 the second string - * @return {@code true} if two string are anagrams, otherwise {@code false} - */ - public static boolean isAnagrams(String s1, String s2) { - int l1 = s1.length(); - int l2 = s2.length(); - s1 = s1.toLowerCase(); - s2 = s2.toLowerCase(); - Map charAppearances = new HashMap<>(); - - for (int i = 0; i < l1; i++) { - char c = s1.charAt(i); - int numOfAppearances = charAppearances.getOrDefault(c, 0); - charAppearances.put(c, numOfAppearances + 1); - } - - for (int i = 0; i < l2; i++) { - char c = s2.charAt(i); - if (!charAppearances.containsKey(c)) { - return false; - } - charAppearances.put(c, charAppearances.get(c) - 1); - } - - for (int cnt : charAppearances.values()) { - if (cnt != 0) { - return false; - } - } - return true; - } - - /** - * If given strings contain Unicode symbols. - * The first 128 ASCII codes are identical to Unicode. - * This algorithm is case-sensitive. - * - * @param s1 the first string - * @param s2 the second string - * @return true if two string are anagrams, otherwise false - */ - public static boolean isAnagramsUnicode(String s1, String s2) { - int[] dict = new int[128]; - for (char ch : s1.toCharArray()) { - dict[ch]++; - } - for (char ch : s2.toCharArray()) { - dict[ch]--; - } - for (int e : dict) { - if (e != 0) { - return false; - } - } - return true; - } - - /** - * If given strings contain only lowercase English letters. - *

- * The main "trick": - * To map each character from the first string 's1' we need to subtract an integer value of 'a' character - * as 'dict' array starts with 'a' character. - * - * @param s1 the first string - * @param s2 the second string - * @return true if two string are anagrams, otherwise false - */ - public static boolean isAnagramsOptimised(String s1, String s2) { - // 26 - English alphabet length - int[] dict = new int[26]; - for (char ch : s1.toCharArray()) { - checkLetter(ch); - dict[ch - 'a']++; - } - for (char ch : s2.toCharArray()) { - checkLetter(ch); - dict[ch - 'a']--; - } - for (int e : dict) { - if (e != 0) { - return false; - } - } - return true; - } - - private static void checkLetter(char ch) { - int index = ch - 'a'; - if (index < 0 || index >= 26) { - throw new IllegalArgumentException("Strings must contain only lowercase English letters!"); - } - } -} diff --git a/src/test/java/com/thealgorithms/strings/AnagramsTest.java b/src/test/java/com/thealgorithms/strings/AnagramsTest.java index 88f6e0bb7..fa8ea72f2 100644 --- a/src/test/java/com/thealgorithms/strings/AnagramsTest.java +++ b/src/test/java/com/thealgorithms/strings/AnagramsTest.java @@ -13,36 +13,37 @@ public class AnagramsTest { private static Stream anagramTestData() { return Stream.of(new AnagramTestCase("late", "tale", true), new AnagramTestCase("late", "teal", true), new AnagramTestCase("listen", "silent", true), new AnagramTestCase("hello", "olelh", true), new AnagramTestCase("hello", "world", false), new AnagramTestCase("deal", "lead", true), - new AnagramTestCase("binary", "brainy", true), new AnagramTestCase("adobe", "abode", true), new AnagramTestCase("cat", "act", true), new AnagramTestCase("cat", "cut", false)); + new AnagramTestCase("binary", "brainy", true), new AnagramTestCase("adobe", "abode", true), new AnagramTestCase("cat", "act", true), new AnagramTestCase("cat", "cut", false), new AnagramTestCase("Listen", "Silent", true), new AnagramTestCase("Dormitory", "DirtyRoom", true), + new AnagramTestCase("Schoolmaster", "TheClassroom", true), new AnagramTestCase("Astronomer", "MoonStarer", true), new AnagramTestCase("Conversation", "VoicesRantOn", true)); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach1(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach1(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsBySorting(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach2(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach2(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsByCountingChars(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach3(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach3(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsByCountingCharsSingleArray(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach4(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach4(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsUsingHashMap(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach5(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach5(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsBySingleFreqArray(testCase.input1(), testCase.input2())); } } diff --git a/src/test/java/com/thealgorithms/strings/CharacterSameTest.java b/src/test/java/com/thealgorithms/strings/CharactersSameTest.java similarity index 100% rename from src/test/java/com/thealgorithms/strings/CharacterSameTest.java rename to src/test/java/com/thealgorithms/strings/CharactersSameTest.java diff --git a/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java b/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java deleted file mode 100644 index 82a75a130..000000000 --- a/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.thealgorithms.strings; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class CheckAnagramsTest { - private static final String MESSAGE = "Strings must contain only lowercase English letters!"; - - // CHECK METHOD isAnagrams() - @Test - public void testCheckAnagrams() { - String testString1 = "STUDY"; - String testString2 = "DUSTY"; - Assertions.assertTrue(CheckAnagrams.isAnagrams(testString1, testString2)); - } - - @Test - public void testCheckFalseAnagrams() { - String testString1 = "STUDY"; - String testString2 = "random"; - Assertions.assertFalse(CheckAnagrams.isAnagrams(testString1, testString2)); - } - - @Test - public void testCheckSameWordAnagrams() { - String testString1 = "STUDY"; - Assertions.assertTrue(CheckAnagrams.isAnagrams(testString1, testString1)); - } - - @Test - public void testCheckDifferentCasesAnagram() { - String testString1 = "STUDY"; - String testString2 = "dusty"; - Assertions.assertTrue(CheckAnagrams.isAnagrams(testString1, testString2)); - } - - // CHECK METHOD isAnagramsUnicode() - // Below tests work with strings which consist of Unicode symbols & the algorithm is case-sensitive. - @Test - public void testStringAreValidAnagramsCaseSensitive() { - Assertions.assertTrue(CheckAnagrams.isAnagramsUnicode("Silent", "liSten")); - Assertions.assertTrue(CheckAnagrams.isAnagramsUnicode("This is a string", "is This a string")); - } - - @Test - public void testStringAreNotAnagramsCaseSensitive() { - Assertions.assertFalse(CheckAnagrams.isAnagramsUnicode("Silent", "Listen")); - Assertions.assertFalse(CheckAnagrams.isAnagramsUnicode("This is a string", "Is this a string")); - } - - // CHECK METHOD isAnagramsOptimised() - // Below tests work with strings which consist of only lowercase English letters - @Test - public void testOptimisedAlgorithmStringsAreValidAnagrams() { - Assertions.assertTrue(CheckAnagrams.isAnagramsOptimised("silent", "listen")); - Assertions.assertTrue(CheckAnagrams.isAnagramsOptimised("mam", "amm")); - } - - @Test - public void testOptimisedAlgorithmShouldThrowExceptionWhenStringsContainUppercaseLetters() { - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> CheckAnagrams.isAnagramsOptimised("Silent", "Listen")); - Assertions.assertEquals(exception.getMessage(), MESSAGE); - - exception = assertThrows(IllegalArgumentException.class, () -> Assertions.assertFalse(CheckAnagrams.isAnagramsOptimised("This is a string", "Is this a string"))); - Assertions.assertEquals(exception.getMessage(), MESSAGE); - } -}