Here is a solution using only primitive java features, that uses a boolean array to mark characters already "hit". It relies on the order of the ASCII Table
, as well as the string having only uppercase characters:
private static void printRepeats(String test) {
// "only upper-case characters" requires options 'A' to 'Z'.
// this is asciiValue('Z') - asciiValue('A') (implicitly done by Java) + 1
boolean[] charHits = new boolean['Z' - 'A' + 1];
for (int i=0; i < test.length(); i++){
// our current character
char current = test.charAt(i);
if (charHits[current - 'A']){
// character index already set in charHits array - already seen!
// print and return
System.out.println(
current + " is repeated, first occurrence "
+ test.indexOf(current) + ", second: " + i
);
return;
} else {
// first hit - mark as already occurred for future iterations
charHits[current - 'A'] = true;
}
}
// no character repeats found
System.out.println("No repeats");
}
The idea is to iterate the string, and every time we see a character, check if we already saw it. If we did, we immediately print and return. If we didn't - we mark it as already "hit" and proceed.
Code walkthrough (printRepeats
):
Part 1 - Array allocation:
To store our "hits" we need a data structure we can lookup past characters. The first statement creates an array just the size we need. Since uppercase letters are continuous in the ASCII table (A
- 65 through Z
- 90), we know we need only a [90 - 65 + 1 = 26] size array to accomodate all uppercase letters. Each character hit is mapped to an index in the array that is [the characters ASCII value] - [A's ASCII value]
. So:
LETTER -> INDEX IN ARRAY
'A' -> 0
'B' -> 1
...
'Z' -> 25
So to allocate such array we use:
boolean[] charHits = new boolean['Z' - 'A' + 1];
This is exactly like:
new boolean[26];
You can check with:
System.out.println('Z' - 'A' + 1); // 26
Or even:
System.out.println(new boolean['Z' - 'A' + 1].length); // 26
Part 2 - String iteration:
This part iterates the given String and has two options:
- Mark a characters as
seen
Spot duplicate, print and return
- It's useful to know that boolean arrays are automatically initialized to
false
, so we can rely on that.
Loop code:
for (int i=0; i < test.length(); i++){
// our current character
char current = test.charAt(i);
if (charHits[current - 'A']){
// character index already set in charHits array - already seen!
// print and return
System.out.println(
current + " is repeated, first occurrence "
+ test.indexOf(current) + ", second repeat: " + i
);
return;
} else {
// first hit - mark as already occurred for future iterations
charHits[current - 'A'] = true;
}
}
// no character repeats found - no index in array hit twice
System.out.println("No repeats");
}
The print part could be further optimized by storing the first occurence's index alongside it's "seen/not seen" status using some clever mapping to a primitive object instead of using String.indexOf
, but it would lessen code readability.
Tip: I don't bother with memorizing specific ASCII Table values (nor should anyone). I only memorize the ASCII Table's order (this case - the uppercase english alphabet being continuous).
Bonus - a short, wasteful, version:
A shorter version can be composed as follows:
For each possible prefix (short to long), check if the first character after the prefix is in the prefix. If it is, that's the first repeat.
The code:
public static void printRepeatsShort(String test){
for (int i=0; i< test.length(); i++){
// check index of following character in prefix
int indexInPrefix = test.substring(0, i).indexOf(test.charAt(i));
if ((indexInPrefix != -1){
// next character is in prefix, return
System.out.println(
"Repeated char: '" + test.charAt(i) + "', First: "
+ indexInPrefix + " Second: " + i
);
return;
}
}
System.out.println("No repeats found");
}
This version is far less optimal, but only relies on methods from String
, so it's more contained.