While reviewing this Scrabble Tile Counter, I decided to give it a try myself. Here is the challenge, from Reddit's /r/dailyprogrammer:
Description
For this challenge we will be using the tile set from the English edition, which has 100 tiles total. Here's a reference for the distribution and point value of each tile.
Each tile will be represented by the letter that appears on it, with the exception that blank tiles are represented by underscores
_
.Input Description
The tiles already in play are inputted as an uppercase string. For example, if 14 tiles have been removed from the bag and are in play, you would be given an input like this:
AEERTYOXMCNB_S
Output Description
You should output the tiles that are left in the bag. The list should be in descending order of the quantity of each tile left in the bag, skipping over amounts that have no tiles.
In cases where more than one letter has the same quantity remaining, output those letters in alphabetical order, with blank tiles at the end.
10: E 9: I 8: A 7: O 5: N, R, T 4: D, L, U 3: G, S 2: F, H, P, V, W 1: B, C, J, K, M, Q, Y, Z, _ 0: X
Solution
The main()
function takes its input as a command-line argument.
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* A collection of Scrabble tiles, initially consisting of the 100 tiles in a
* standard Scrabble game, from which tiles can be removed, and the remaining
* counts reported. Tiles may be uppercase 'A' to 'Z', or a '_' to represent
* a blank tile.
*/
public class ScrabbleTileCounter implements Iterable<Integer> {
private static final int[] START_COUNTS = {
/* A */ 9, /* B */ 2, 2, 4, 12, 2, 3, 2, 9, 1, 1, 4, /* M */ 2,
/* N */ 6, /* O */ 8, 2, 1, 6, 4, 6, 4, 2, 2, 1, 2, /* Z */ 1,
/* junk */ -1, /* junk */ -1, /* junk */ -1, /* junk */ -1,
/* _ */ 2
};
private int[] counts = Arrays.copyOf(START_COUNTS, START_COUNTS.length);
public void remove(char c) throws NoSuchElementException {
int i = c - 'A';
if (i < 0 || i >= this.counts.length || this.counts[i] <= 0) {
throw new NoSuchElementException(String.valueOf(c));
}
this.counts[i]--;
}
/**
* Returns the characters, in ASCIIbetical order, that appear with the
* specified frequency.
*/
public String charactersWithCount(int n) {
if (n < 0) return "";
return IntStream.range(0, this.counts.length)
.filter(i -> this.counts[i] == n)
.mapToObj(i -> String.valueOf((char)('A' + i)))
.collect(Collectors.joining());
}
/**
* A countdown of the frequencies of the remaining tiles.
*/
public Iterator<Integer> iterator() {
return new CountIterator();
}
private class CountIterator implements Iterator<Integer> {
int n;
CountIterator() {
this.n = Integer.MAX_VALUE;
this.next();
}
public boolean hasNext() {
return this.n >= 0;
}
public Integer next() {
try {
return this.n;
} finally {
this.n = Arrays.stream(ScrabbleTileCounter.this.counts)
.filter(x -> x < n)
.max()
.orElse(-1);
}
}
}
public static void main(String[] args) {
ScrabbleTileCounter scrabble = new ScrabbleTileCounter();
try {
for (char c : args[0].toCharArray()) {
scrabble.remove(c);
}
for (int n : scrabble) {
System.out.printf("%2d: %s\n",
n,
String.join(", ", scrabble.charactersWithCount(n).split(""))
);
}
} catch (NoSuchElementException e) {
System.err.println("Invalid input. More " + e.getMessage() +
"'s have been taken from the bag than possible.");
System.exit(1);
}
}
}