Over a million developers have joined DZone.

Java Code Solution: Scrabble Sets

DZone 's Guide to

Java Code Solution: Scrabble Sets

This challenge has proven that there are many ways to skin a cat.

· Java Zone ·
Free Resource

If you've not seen the original challenge, then head over to here and check it out. In summary, given an entire bag of scrabble letters, and a set of letters that had been taken out, calculate and print which letters are left. Pretty simple right?

What I love about the solutions to this challenge is just how different all of the solutions are. Like, utterly and completely different. There's everything from one class main methods through to full domain model behamoths. It's fascinating and a wonderful example of how complex our jobs are as programmers. There are infinite ways of solving even the simplest problems.

For examples, check out this Gist for the "simplest" solution (one method, about eight lines of code) and this full domain model solution that has a staggering seven classes for the solution!

I'll come onto what I think is "right" in a moment, but I'd also like to highlight one of the other big takeaway the solutions to this challenge has thrown up—Java 8 has made everything so much simpler and more complicated. Pretty much all the solutions have used the new Java 8 Stream syntax, but pretty much every solution has used it in a different way. There are so many different options to cut this problem up. I highly recommend flicking through some of the solutions (all in the comments) to see the extent of this.

Complex Domain Model vs. Simplicity

This is a battle that we all face when developing systems. Do we build full object oriented domain models or just cram the code into a single method and be done with it? As with all programming, the answer is "it depends," and an answer will usually lie in the middle. Let's check out the one-class solution from Daniel Scherwing.

package software.schwering.javacodechallenge;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class ScrabbleSets {

private static final Map<Character, Integer> COUNT_BY_LETTER = new HashMap<>();
//see http://scrabblewizard.com/scrabble-tile-distribution/
COUNT_BY_LETTER.put('A', 9);
COUNT_BY_LETTER.put('B', 2);
COUNT_BY_LETTER.put('C', 2);
COUNT_BY_LETTER.put('D', 4);
COUNT_BY_LETTER.put('E', 12);
COUNT_BY_LETTER.put('F', 2);
COUNT_BY_LETTER.put('G', 3);
COUNT_BY_LETTER.put('H', 2);
COUNT_BY_LETTER.put('I', 9);
COUNT_BY_LETTER.put('J', 1);
COUNT_BY_LETTER.put('K', 1);
COUNT_BY_LETTER.put('L', 4);
COUNT_BY_LETTER.put('M', 2);
COUNT_BY_LETTER.put('N', 6);
COUNT_BY_LETTER.put('O', 8);
COUNT_BY_LETTER.put('P', 2);
COUNT_BY_LETTER.put('Q', 1);
COUNT_BY_LETTER.put('R', 6);
COUNT_BY_LETTER.put('S', 4);
COUNT_BY_LETTER.put('T', 6);
COUNT_BY_LETTER.put('U', 4);
COUNT_BY_LETTER.put('V', 2);
COUNT_BY_LETTER.put('W', 2);
COUNT_BY_LETTER.put('X', 1);
COUNT_BY_LETTER.put('Y', 2);
COUNT_BY_LETTER.put('Z', 1);
COUNT_BY_LETTER.put('_', 2);

public static void printRemainingTiles(String tilesInPlay){
Map<Character, Integer> remainingCount = new HashMap<>(COUNT_BY_LETTER);
tilesInPlay.chars().forEach(i->remainingCount.put((char)i, remainingCount.get((char)i)-1));
List<Character> errors = remainingCount.entrySet().stream().filter(e->e.getValue()<0).map(Entry::getKey).collect(Collectors.toList());
Map<Integer, String> tilesByCount = remainingCount.entrySet().stream().collect(Collectors.groupingBy(Entry::getValue, Collectors.mapping(Entry::getKey, Collectors.mapping(String::valueOf,Collectors.joining(", ")))));
tilesByCount.entrySet().stream().sorted((e1,e2)->e2.getKey().compareTo(e1.getKey())).forEachOrdered(e->System.out.printf("%s: %s\n",e.getKey(),e.getValue()));
errors.forEach(c->System.out.printf("Invalid input. More %s's have been taken from the bag than possible.\n",c));


Now, this is an entirely fully functional solution. And, for the case of a simple DZone challenge, is fine and dandy. However, in terms of longer term maintainable code, it could do with a little bit of work.  At the moment that's a fairly daunting block of code. Even just adding some spaces in there would make it easier to process, but I think I'd go an extra step and extract some methods out to make it clear and readable; specifically removeLettersFromBag(), validateInput() and createOutput().

Is there any need to go further than this in our breakdown? A number of solutions pulled out Tile as a specific object, maintaining the count of that tile, which is certainly a nice way of encapsulating that knowledge instead of using a Map and forcing the reader to understand that knowledge in their head. Take this example from Christian Groth:

public class ScrabbleSetTile {
    private final char key;
    private int available;

    public ScrabbleSetTile(char key, int available) {
        this.key = key;
        this.available = available;

    public void decrease(int amount) {
        available -= amount;

    public boolean isValid() {
        return available >= 0;

    public char getKey() {
        return key;

    public int getAvailable() {
        return available;

... in the main class...

  public ScrabbleSetTilesReport generateTilesReport() {

        // find all tiles in error state
        Set<Character> tilesInErrorState = tiles.stream().filter(t -> !t.isValid()).map(t -> t.getKey()).collect(Collectors.toSet());

        // group tiles by available count, descending
        NavigableMap<Integer, Set<Character>> tilesByAvailabilityCount = tiles.stream().filter(t -> t.isValid())
                .collect(Collectors.toMap((ScrabbleSetTile t) -> t.getAvailable(), (ScrabbleSetTile t) -> {
                    Set<Character> set = new TreeSet<Character>();
                    return set;
                } , (Set<Character> a, Set<Character> b) -> {
                    return a;
                } , TreeMap::new)).descendingMap();

        // done
        return new ScrabbleSetTilesReport(tilesByAvailabilityCount, tilesInErrorState);

This makes the cognitive load much easier to process—instead of operating on maps we're operating on Tile objects which makes the understanding (and ergo maintainability) much easier. I don't think there is much need to go deeper on extracting Objects though.  The ideal set up for me is:

  • Scrabble Bag: containing all tiles and the functionality to remove tiles

  • BagOutput: a seperate class to handle turning the state of the Scrabble Bag into a pretty printed output

  • Tile: Stateful Object representing each tile

Most haven't used a seperate output class, which is just a personal preference but a nice way to seperate concerns.

Exceptional circumstances

One of the interesting trends was a lot of developers created a specific Exception for the scenario where the input was incorrect due to taking a letter out too many times. Here is one example from wlmitch.

package com.sudexpress.test.dzone.scrabblesets;

public class NotEnoughTilesException extends Exception {

private static final long serialVersionUID = 2442036946923671415L;

public NotEnoughTilesException(final String letter) {
super(String.format("Invalid input. More %s's have been taken from the bag than possible.", letter));

.. in the main class...

void pickOne(final String letter) throws NotEnoughTilesException {

public String remainingLetters(final String letters) {
try {
final Map<Integer, List<Tile>> tilesByCounts = this.groupTilesByCounts();
return String.join("\n", this.outputLines(tilesByCounts));
} catch (final NotEnoughTilesException e) {
return e.getMessage();

Personally I'm not a fan of this. This is not an "exceptional" circumstance: We can reasonably expect, when designing the system, the input may be invalid. In this scenario, we can just handle it as part of our normal code flow, without the need for an Exception. Exceptions are for exceptional circumstances!

Well done to everyone who submitted a solution for this challenge!

code challenge ,java 8 ,java 8 collections

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}