Visit the robot-name exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
Randomly add to used names
Randomly add to used names
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
public class Robot {
private static final Set < String > usedNames = new HashSet();
private static final Random random = new Random();
private String name;
public Robot() {
reset();
}
public String getName() {
return name;
}
public void reset() {
usedNames.remove(this.name);
String name;
while (usedNames.contains(name = generateName())) {
continue;
}
usedNames.add(name);
this.name = name;
}
private static String randomString(char fromChar, char toChar, int len) {
return random.ints(fromChar, toChar + 1).limit(len)
.mapToObj(ch -> Character.toString((char) ch))
.collect(Collectors.joining());
}
private static String generateName() {
return randomString('A', 'Z', 2) + randomString('0', '9', 3);
}
}
This approach starts by importing from packages for what will be needed.
A HashSet to hold the used names is defined as private static and final.
It is private because it doesn’t need to be seen outside the class.
It is static because it doesn’t need to differ between object instantiations, so it can belong to the class itself.
It is final because it does not need to be redefined after it is created.
A Random object is instantiated the same way.
The high-level work is done in the reset() method.
At the time of this writing the tests don’t specify if names can be re-used.
However, they do test that a reset name is not the same as the old name.
By removing the used name first, a slight possibility exists that the new randomly generated name could be the same as the old name
and fail the test.
If removing a name from the used names, it might be best to delay doing it until after the new name is generated.
reset() will keep randomly generating a new name until it is not contained in the used names.
As the collection of used names increases in size, the chance for collision between a new name and a used name also increases.
After thousands of names have been generated, a lot of processing time can be spent generating names
that have already been used.
It will take more and more tries before generating a name that hasn’t been used.
The low-level work is done by the randomString() method.
It uses the Random.ints() method which takes a lower bound (inclusive) and upper bound (exclusive).
For example, if passed in a fromChar of A and a toChar of Z, 1 is added to toChar so that the random int
will be an ASCII value picked from A through Z.
The mapToObj() method is used to convert the ASCII value for the char into a String.
The joining Collector is passed to the collect() method to assemble the returned String.
randomString() is called by the generateName() method to return two letters from A through Z and three digits from 0 through 9.
A possible optimization could be to generate the numeric part of the name as one number instead of three separate digits.
String.format("%03d") would come in handy for that.
The %03d format specifier indicates having leading zeros for a number up to three places,
so the number 1 would be formated as 001 and the number 999, already taking three places, would have no leading zeroes.
Sequentially take from shuffled names
Sequentially take from shuffled names
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
import java.util.stream.IntStream;
class Robot {
private String name = RobotNameGen.GetName();
public void reset() {
name = RobotNameGen.GetName();
}
public String getName() {
return name;
}
static class RobotNameGen {
private static int namesIndex = -1;
private static List < String > names = new ArrayList();
static {
IntStream.rangeClosed('A', 'Z')
.forEach(letter1 -> IntStream.rangeClosed('A', 'Z').forEach(letter2 ->
IntStream.range(0, 1000).forEach(num ->
names.add(String.format("%c%c%03d", (char) letter1, (char) letter2, num)))));
Collections.shuffle(names, new Random(System.currentTimeMillis()));
}
public static String GetName() {
return names.get(++namesIndex);
}
}
}
This approach starts by importing from packages for what will be needed.
The work of generating the names is done by the nested static class RobotNameGen inside the Robot class.
It is static because it doesn’t need to differ between object instantiations, so it can belong to the Robot class itself.
It defines an ArrayList to hold the names, defined as private, since it doesn’t need to be seen outside the class.
It also defines an index to be used for getting an element from the ArrayList.
A static initialization block is used for populating the ArrayList.
The IntStream.rangeClosed() method is used for generating an ASCII value from A through Z.
In a forEach() method, the first letter is passed to a lambda function which in turn calls IntStream.rangeClosed()
to generate the second letter.
In another forEach(), the second letter is passed in a lambda function that calls IntStream.range() to generate
a number from 0 up to but not including 1000.
Note that the difference between `IntStream range()` and `IntStream rangeClosed()` is that the upper bound is _exclusive_
for `range()` and is _inclusive_ for `rangeClosed()`.
So, `IntStream.range(1, 10)` iterates from `1` up to but not including `10`,
and `IntStream.rangeClosed(1, 10)` iterates from `1` through `10`.
The number is passed to a lambda which formats the two ASCII values and the number into a string.
The %03d format specifier indicates having leading zeros for a number up to three places,
so the number 1 would be formated as 001 and the number 999, already taking three places, would have no leading zeroes.
The formatted string is added to the ArrayList.
After all possible names are in the ArrayList, the ArrayList is shuffled,
seeding it with a new Random object initialized with the current time in milliseconds.
Whenever a Robot object needs a new name, it calls the RobotNameGen.GetName() method.
The method increments the index and gets the name from the ArrayList at that index.
Since the index is initialized with -1, the first name will be taken from index 0.
Source: Exercism java/robot-name