Skip to content

Commit 254ac87

Browse files
added isogram utility class and unit tests
Signed-off-by: JeevaRamanathan <jeevaramanathan.m@infosys.com>
1 parent d8ddb07 commit 254ac87

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 class Isogram {
29+
/**
30+
* Checks if a string is an isogram using boolean array approach.
31+
*
32+
* Time Complexity: O(n)
33+
* Space Complexity: O(1)
34+
*
35+
* @param str the input string
36+
* @return true if the string is an isogram, false otherwise
37+
* @throws IllegalArgumentException if the string contains non-alphabetic
38+
* characters
39+
*/
40+
public static boolean isIsogramByArray(String str) {
41+
if (str == null || str.isEmpty()) {
42+
return true;
43+
}
44+
45+
String lowerStr = str.toLowerCase();
46+
boolean[] seenChars = new boolean[26];
47+
48+
for (int i = 0; i < lowerStr.length(); i++) {
49+
char ch = lowerStr.charAt(i);
50+
51+
// Check if character is a letter
52+
if (ch >= 'a' && ch <= 'z') {
53+
int index = ch - 'a';
54+
if (seenChars[index]) {
55+
return false; // Letter already seen
56+
}
57+
seenChars[index] = true;
58+
}
59+
}
60+
return true;
61+
}
62+
63+
/**
64+
* Checks if a string is an isogram using length comparison approach.
65+
* Time Complexity: O(n)
66+
* Space Complexity: O(k) where k is the number of unique characters
67+
*
68+
* @param str the input string
69+
* @return true if the string is an isogram, false otherwise
70+
*/
71+
public static boolean isIsogramByLength(String str) {
72+
if (str == null || str.isEmpty()) {
73+
return true;
74+
}
75+
str = str.toLowerCase();
76+
77+
Set<Character> uniqueChars = new HashSet<>();
78+
for (char ch : str.toCharArray()) {
79+
uniqueChars.add(ch);
80+
}
81+
return uniqueChars.size() == str.length();
82+
}
83+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.stream.Stream;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.MethodSource;
8+
import org.junit.jupiter.api.Test;
9+
10+
public class IsogramTest {
11+
12+
record IsogramTestCase(String input, boolean expected) {
13+
}
14+
15+
private static Stream<IsogramTestCase> isogramArrayTestData() {
16+
return Stream.of(
17+
// Valid isograms (only checks letters)
18+
new IsogramTestCase("uncopyrightable", true),
19+
new IsogramTestCase("dermatoglyphics", true),
20+
new IsogramTestCase("background", true),
21+
new IsogramTestCase("python", true),
22+
new IsogramTestCase("keyboard", true),
23+
new IsogramTestCase("clipboard", true),
24+
new IsogramTestCase("flowchart", true),
25+
new IsogramTestCase("bankruptcy", true),
26+
new IsogramTestCase("computer", true),
27+
new IsogramTestCase("algorithms", true),
28+
29+
// Not isograms - letters repeat
30+
new IsogramTestCase("hello", false),
31+
new IsogramTestCase("programming", false),
32+
new IsogramTestCase("java", false),
33+
new IsogramTestCase("coffee", false),
34+
new IsogramTestCase("book", false),
35+
new IsogramTestCase("letter", false),
36+
new IsogramTestCase("mississippi", false),
37+
new IsogramTestCase("google", false),
38+
39+
// Edge cases
40+
new IsogramTestCase("", true),
41+
new IsogramTestCase("a", true),
42+
new IsogramTestCase("ab", true),
43+
new IsogramTestCase("abc", true),
44+
new IsogramTestCase("aa", false),
45+
new IsogramTestCase("abcdefghijklmnopqrstuvwxyz", true), // All 26 letters
46+
47+
// Case insensitive
48+
new IsogramTestCase("Python", true),
49+
new IsogramTestCase("BACKGROUND", true),
50+
new IsogramTestCase("Hello", false),
51+
new IsogramTestCase("PROGRAMMING", false));
52+
}
53+
54+
private static Stream<IsogramTestCase> isogramLengthTestData() {
55+
return Stream.of(
56+
// Valid isograms (checks all characters)
57+
new IsogramTestCase("uncopyrightable", true),
58+
new IsogramTestCase("dermatoglyphics", true),
59+
new IsogramTestCase("background", true),
60+
new IsogramTestCase("python", true),
61+
new IsogramTestCase("keyboard", true),
62+
new IsogramTestCase("clipboard", true),
63+
new IsogramTestCase("flowchart", true),
64+
new IsogramTestCase("bankruptcy", true),
65+
new IsogramTestCase("computer", true),
66+
new IsogramTestCase("algorithms", true),
67+
68+
// Not isograms - characters repeat
69+
new IsogramTestCase("hello", false),
70+
new IsogramTestCase("programming", false),
71+
new IsogramTestCase("java", false),
72+
new IsogramTestCase("coffee", false),
73+
new IsogramTestCase("book", false),
74+
new IsogramTestCase("letter", false),
75+
new IsogramTestCase("mississippi", false),
76+
new IsogramTestCase("google", false),
77+
78+
// Edge cases
79+
new IsogramTestCase("", true),
80+
new IsogramTestCase("a", true),
81+
new IsogramTestCase("ab", true),
82+
new IsogramTestCase("abc", true),
83+
new IsogramTestCase("aa", false),
84+
new IsogramTestCase("abcdefghijklmnopqrstuvwxyz", true), // All 26 letters
85+
86+
// Case insensitive
87+
new IsogramTestCase("Python", true),
88+
new IsogramTestCase("BACKGROUND", true),
89+
new IsogramTestCase("Hello", false),
90+
new IsogramTestCase("PROGRAMMING", false),
91+
92+
// Strings with symbols and numbers
93+
new IsogramTestCase("abc@def", true), // all characters unique
94+
new IsogramTestCase("test-case", false), // 't', 's', 'e' repeat
95+
new IsogramTestCase("python123", true), // all characters unique
96+
new IsogramTestCase("hello@123", false), // 'l' repeats
97+
new IsogramTestCase("abc123!@#", true), // all characters unique
98+
new IsogramTestCase("test123test", false), // 't', 'e', 's' repeat
99+
new IsogramTestCase("1234567890", true), // all digits unique
100+
new IsogramTestCase("12321", false), // '1' and '2' repeat
101+
new IsogramTestCase("!@#$%^&*()", true) // all special characters unique
102+
);
103+
}
104+
105+
@ParameterizedTest
106+
@MethodSource("isogramArrayTestData")
107+
void testIsogramByArray(IsogramTestCase testCase) {
108+
assertEquals(testCase.expected(), Isogram.isIsogramByArray(testCase.input()));
109+
}
110+
111+
@ParameterizedTest
112+
@MethodSource("isogramLengthTestData")
113+
void testIsogramByLength(IsogramTestCase testCase) {
114+
assertEquals(testCase.expected(), Isogram.isIsogramByLength(testCase.input()));
115+
}
116+
117+
@Test
118+
void testNullInputByArray() {
119+
assertEquals(true, Isogram.isIsogramByArray(null));
120+
}
121+
122+
@Test
123+
void testNullInputByLength() {
124+
assertEquals(true, Isogram.isIsogramByLength(null));
125+
}
126+
127+
@Test
128+
void testEmptyStringByArray() {
129+
assertEquals(true, Isogram.isIsogramByArray(""));
130+
}
131+
132+
@Test
133+
void testEmptyStringByLength() {
134+
assertEquals(true, Isogram.isIsogramByLength(""));
135+
}
136+
}

0 commit comments

Comments
 (0)