Skip to content

Commit 7e29be3

Browse files
Added isogram utility class and unit tests (TheAlgorithms#6594)
* added isogram utility class and unit tests Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * checking lint issue Signed-off-by: JeevaRamanathan <[email protected]> * linter fix Signed-off-by: JeevaRamanathan <[email protected]> * fix: HideUtilityClassConstructor Signed-off-by: JeevaRamanathan <[email protected]> * linter fix Signed-off-by: JeevaRamanathan <[email protected]> * updated isogram class to final Signed-off-by: JeevaRamanathan <[email protected]> * pipeline bug fixing Signed-off-by: JeevaRamanathan <[email protected]> * renamed function names and few fixes w.r.t to suggestion Signed-off-by: JeevaRamanathan <[email protected]> --------- Signed-off-by: JeevaRamanathan <[email protected]>
1 parent 8d14b49 commit 7e29be3

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.thealgorithms.strings;
2+
3+
import java.util.HashSet;
4+
import java.util.Set;
5+
6+
/**
7+
* An isogram (also called heterogram or nonpattern word) is a word in which no
8+
* letter of the word occurs more than once. Each character appears exactly
9+
* once.
10+
*
11+
* For example, the word "uncopyrightable" is the longest common English isogram
12+
* with 15 unique letters. Other examples include "dermatoglyphics" (15
13+
* letters),
14+
* "background" (10 letters), "python" (6 letters), and "keyboard" (8 letters).
15+
* But words like "hello" and "programming" are not isograms because some
16+
* letters
17+
* appear multiple times ('l' appears twice in "hello", while 'r', 'm', 'g'
18+
* repeat
19+
* in "programming").
20+
*
21+
* Isograms are particularly valuable in creating substitution ciphers and are
22+
* studied in recreational linguistics. A perfect pangram, which uses all 26
23+
* letters
24+
* of the alphabet exactly once, is a special type of isogram.
25+
*
26+
* Reference from https://en.wikipedia.org/wiki/Heterogram_(literature)#Isograms
27+
*/
28+
public final class Isogram {
29+
/**
30+
* Private constructor to prevent instantiation of utility class.
31+
*/
32+
private Isogram() {
33+
}
34+
35+
/**
36+
* Checks if a string is an isogram using boolean array approach.
37+
*
38+
* Time Complexity: O(n)
39+
* Space Complexity: O(1)
40+
*
41+
* @param str the input string
42+
* @return true if the string is an isogram, false otherwise
43+
* @throws IllegalArgumentException if the string contains non-alphabetic
44+
* characters
45+
*/
46+
public static boolean isAlphabeticIsogram(String str) {
47+
if (str == null || str.isEmpty()) {
48+
return true;
49+
}
50+
51+
str = str.toLowerCase();
52+
53+
for (int i = 0; i < str.length(); i++) {
54+
char ch = str.charAt(i);
55+
if (ch < 'a' || ch > 'z') {
56+
throw new IllegalArgumentException("Input contains non-alphabetic character: '" + ch + "'");
57+
}
58+
}
59+
60+
boolean[] seenChars = new boolean[26];
61+
for (int i = 0; i < str.length(); i++) {
62+
char ch = str.charAt(i);
63+
int index = ch - 'a';
64+
if (seenChars[index]) {
65+
return false;
66+
}
67+
seenChars[index] = true;
68+
}
69+
return true;
70+
}
71+
72+
/**
73+
* Checks if a string is an isogram using length comparison approach.
74+
* Time Complexity: O(n)
75+
* Space Complexity: O(k) where k is the number of unique characters
76+
*
77+
* @param str the input string
78+
* @return true if the string is an isogram, false otherwise
79+
*/
80+
public static boolean isFullIsogram(String str) {
81+
if (str == null || str.isEmpty()) {
82+
return true;
83+
}
84+
str = str.toLowerCase();
85+
86+
Set<Character> uniqueChars = new HashSet<>();
87+
for (char ch : str.toCharArray()) {
88+
uniqueChars.add(ch);
89+
}
90+
return uniqueChars.size() == str.length();
91+
}
92+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import java.util.stream.Stream;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.params.ParameterizedTest;
11+
import org.junit.jupiter.params.provider.MethodSource;
12+
13+
public class IsogramTest {
14+
15+
record IsogramTestCase(String input, boolean expected) {
16+
}
17+
18+
private static Stream<IsogramTestCase> isAlphabeticIsogram() {
19+
return Stream.of(
20+
// Valid isograms (only checks letters)
21+
new IsogramTestCase("uncopyrightable", true), new IsogramTestCase("dermatoglyphics", true), new IsogramTestCase("background", true), new IsogramTestCase("python", true), new IsogramTestCase("keyboard", true), new IsogramTestCase("clipboard", true), new IsogramTestCase("flowchart", true),
22+
new IsogramTestCase("bankruptcy", true), new IsogramTestCase("computer", true), new IsogramTestCase("algorithms", true),
23+
24+
// Not isograms - letters repeat
25+
new IsogramTestCase("hello", false), new IsogramTestCase("programming", false), new IsogramTestCase("java", false), new IsogramTestCase("coffee", false), new IsogramTestCase("book", false), new IsogramTestCase("letter", false), new IsogramTestCase("mississippi", false),
26+
new IsogramTestCase("google", false),
27+
28+
// Edge cases
29+
new IsogramTestCase("", true), new IsogramTestCase("a", true), new IsogramTestCase("ab", true), new IsogramTestCase("abc", true), new IsogramTestCase("aa", false), new IsogramTestCase("abcdefghijklmnopqrstuvwxyz", true),
30+
31+
// Case insensitive
32+
new IsogramTestCase("Python", true), new IsogramTestCase("BACKGROUND", true), new IsogramTestCase("Hello", false), new IsogramTestCase("PROGRAMMING", false));
33+
}
34+
35+
private static Stream<IsogramTestCase> isFullIsogram() {
36+
return Stream.of(
37+
// Valid isograms (checks all characters)
38+
new IsogramTestCase("uncopyrightable", true), new IsogramTestCase("dermatoglyphics", true), new IsogramTestCase("background", true), new IsogramTestCase("python", true), new IsogramTestCase("keyboard", true), new IsogramTestCase("clipboard", true), new IsogramTestCase("flowchart", true),
39+
new IsogramTestCase("bankruptcy", true), new IsogramTestCase("computer", true), new IsogramTestCase("algorithms", true),
40+
41+
// Not isograms - characters repeat
42+
new IsogramTestCase("hello", false), new IsogramTestCase("programming", false), new IsogramTestCase("java", false), new IsogramTestCase("coffee", false), new IsogramTestCase("book", false), new IsogramTestCase("letter", false), new IsogramTestCase("mississippi", false),
43+
new IsogramTestCase("google", false),
44+
45+
// Edge cases
46+
new IsogramTestCase("", true), new IsogramTestCase("a", true), new IsogramTestCase("ab", true), new IsogramTestCase("abc", true), new IsogramTestCase("aa", false), new IsogramTestCase("abcdefghijklmnopqrstuvwxyz", true),
47+
48+
// Case insensitive
49+
new IsogramTestCase("Python", true), new IsogramTestCase("BACKGROUND", true), new IsogramTestCase("Hello", false), new IsogramTestCase("PROGRAMMING", false),
50+
51+
// Strings with symbols and numbers
52+
new IsogramTestCase("abc@def", true), // all characters unique
53+
new IsogramTestCase("test-case", false), // 't', 's', 'e' repeat
54+
new IsogramTestCase("python123", true), // all characters unique
55+
new IsogramTestCase("hello@123", false), // 'l' repeats
56+
new IsogramTestCase("abc123!@#", true), // all characters unique
57+
new IsogramTestCase("test123test", false), // 't', 'e', 's' repeat
58+
new IsogramTestCase("1234567890", true), // all digits unique
59+
new IsogramTestCase("12321", false), // '1' and '2' repeat
60+
new IsogramTestCase("!@#$%^&*()", true) // all special characters unique
61+
);
62+
}
63+
64+
@ParameterizedTest
65+
@MethodSource("isAlphabeticIsogram")
66+
void testIsogramByArray(IsogramTestCase testCase) {
67+
assertEquals(testCase.expected(), Isogram.isAlphabeticIsogram(testCase.input()));
68+
}
69+
70+
@ParameterizedTest
71+
@MethodSource("isFullIsogram")
72+
void testIsogramByLength(IsogramTestCase testCase) {
73+
assertEquals(testCase.expected(), Isogram.isFullIsogram(testCase.input()));
74+
}
75+
76+
@Test
77+
void testNullInputByArray() {
78+
assertTrue(Isogram.isAlphabeticIsogram(null));
79+
}
80+
81+
@Test
82+
void testNullInputByLength() {
83+
assertTrue(Isogram.isFullIsogram(null));
84+
}
85+
86+
@Test
87+
void testEmptyStringByArray() {
88+
assertTrue(Isogram.isAlphabeticIsogram(""));
89+
}
90+
91+
@Test
92+
void testEmptyStringByLength() {
93+
assertTrue(Isogram.isFullIsogram(""));
94+
}
95+
96+
@Test
97+
void testAlphabeticIsogramThrowsException() {
98+
// Test that IllegalArgumentException is thrown for non-alphabetic characters
99+
assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("1"));
100+
assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("@"));
101+
assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("python!"));
102+
assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("123algorithm"));
103+
assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("hello123"));
104+
assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("!@@#$%^&*()"));
105+
}
106+
107+
@Test
108+
void testFullIsogramWithMixedCharacters() {
109+
// Test that full isogram method handles all character types without exceptions
110+
assertTrue(Isogram.isFullIsogram("abc123"));
111+
assertFalse(Isogram.isFullIsogram("test@email")); // 'e' repeats
112+
assertFalse(Isogram.isFullIsogram("hello123")); // 'l' repeats
113+
assertTrue(Isogram.isFullIsogram("1234567890"));
114+
assertFalse(Isogram.isFullIsogram("12321")); // '1' and '2' repeat
115+
assertTrue(Isogram.isFullIsogram("!@#$%^&*()"));
116+
}
117+
}

0 commit comments

Comments
 (0)