refactor: unified duplicate Anagram classes into a single implementation (#6290)

This commit is contained in:
Deniz Altunkapan
2025-06-11 19:04:06 +02:00
committed by GitHub
parent 0b21bb0a38
commit 1745d19f09
9 changed files with 38 additions and 212 deletions

View File

@ -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() {

View File

@ -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() {
}

View File

@ -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() {
}

View File

@ -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/

View File

@ -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;
}

View File

@ -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<Character, Integer> 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.
* <p>
* 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!");
}
}
}

View File

@ -13,36 +13,37 @@ public class AnagramsTest {
private static Stream<AnagramTestCase> 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()));
}
}

View File

@ -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);
}
}