3
\$\begingroup\$

I am currently implementing a Poker game, I have created all the logic for getting the rank of a hand, and I now have to determine the winner of a given hand.

Due to this being Poker, there may be multiple winners in a round. I, therefore, need to find all the keys (Player) that have the best hand (regardless of how many there are).

   public Map<Player, Hand> determineWinner(Map<Player, Hand> hands) {
        // Initialize worst possible hand (any hand beats this) - less verbose than
        // having to get a random element form the map
        Hand currentBestHand = new Hand(RankType.HIGH_CARD, CardRank.DEFAULT, new ArrayList<>(), new ArrayList<>());
        Map<Player, Hand> bestHands = new HashMap<>();
        for (Map.Entry<Player, Hand> playerAndHand : hands.entrySet()) {
            int comparisonValue = playerAndHand.getValue().compareTo(currentBestHand);
            if (comparisonValue > 0) {
                currentBestHand = playerAndHand.getValue();
                bestHands.clear();
                bestHands.put(playerAndHand.getKey(), playerAndHand.getValue());
            } else if (comparisonValue == 0) {
                bestHands.put(playerAndHand.getKey(), playerAndHand.getValue());
            }
        }
        return bestHands;
    }

However, I am using Java 8 and thus a lot of streams. I couldn't figure out a (pretty) way to implementing the above in streams, and therefore ended up coding it in a loop. I welcome any suggestions, especially those related to writing it succinctly and efficiently using Java 8 streams.

\$\endgroup\$
3
\$\begingroup\$

To use streams you'd have to make the score part of the object (at least on an intermediate level) so that you can find the maximum based on the score.

Here's a little example based on a string/string map and String.length() as score replacement (as I don't have your original objects). Should be sufficient to illustrate the idea:

    Map<String, String> m = ...

    Map<Integer, List<Entry<String, String>>> intermediateByScore = m.entrySet().stream()
        .collect(Collectors.groupingBy(e -> e.getValue().length()));

    Entry<Integer, List<Entry<String, String>>> highest = intermediateByScore.entrySet().stream()
        .sorted((e1, e2) -> e2.getKey().compareTo(e1.getKey()))
        .findFirst()
        .get();

    Map<String, String> result = highest.getValue().stream()
        .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));

... or, after a few inlines of intermediate variables:

    Map<String, String> result = m.entrySet().stream()
        .collect(Collectors.groupingBy(e -> e.getValue().length()))
        .entrySet().stream()
        .sorted((e1, e2) -> e2.getKey().compareTo(e1.getKey()))
        .findFirst()
        .get().getValue().stream()
        .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));

Is this better than the loop: I don't personally think so. My advice: stick to your loop or revise your data structures to better support this approach.

\$\endgroup\$
1
\$\begingroup\$

Constants

// Initialize worst possible hand (any hand beats this)...
Hand currentBestHand = new Hand(RankType.HIGH_CARD, CardRank.DEFAULT, 
                                new ArrayList<>(), new ArrayList<>());

This sounds like a good candidate for creating a constant LOWEST_HAND, as an example:

public class Hand implements Comparable<Hand> {

    public static final Hand LOWEST_HAND = new Hand(RankType.HIGH_CARD, CardRank.DEFAULT, 
                                                    emptyList(), emptyList());

    // ...
}

Over here, I have opted for the use of Collections.emptyList() to make it clearer that anything is 'better' than an empty, immutable List.

Sorting with a TreeMap

You can consider using a TreeMap to slightly simplify the sorting of Hand for you:

TreeMap<Hand, Map<Player, Hand>>> map = hands.entrySet().stream().collect(
                                    groupingBy(Entry::getValue, TreeMap::new, toMap(
                                                    Entry::getKey, Entry::getValue)));

Similar to @mtj's answer, we use Collectors.groupingBy(Function, Supplier, Collector) with the additional parameters to specify the Map supplier and the downstream Collector with Collectors.toMap(Function, Function).

With the TreeMap, we can then use TreeMap.lastEntry() to get the entry with the largest Hand and return the desired Map output:

public static Map<Player, Hand> determineWinner(Map<Player, Hand> hands) {
    return hands.entrySet().stream()
            .collect(groupingBy(Entry::getValue, TreeMap::new,
                                toMap(Entry::getKey, Entry::getValue)))
            .lastEntry()
            .getValue();
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.