3

I'm almost done finishing my poker project for university but I'm still struggeling a bit with java streams. I wrote a HandEvaluator Class which calculates the strenght of each players hand and assigns it to the player. Now I'm trying to add the player or players (if more than one player have the same score / split pot) with the highest hand score to a list to calculate the winnigs.

I'm having issues with the syntax of streams. I'm trying something like this:

playerList.stream().max(Comparator.comparing(Player::getHandScore)).get();

This is returning the Player with the highest score, but what if there are multiple with the same score? And how do i add them to a list?

ping pong
  • 225
  • 6
  • 16

5 Answers5

4

I did something like this. I group by the score and then find the score with maximum key value. It will return an Optional of Map.Entry. It contains both the maximum value and the players that have it. Then I can get the list of players using getValue() method.

List<Player> value = playerList.stream()
        .collect(groupingBy(Player::getScore))
        .entrySet()
        .stream()
        .max(Comparator.comparing(Map.Entry::getKey))
        .get()
        .getValue();
Mansur
  • 1,661
  • 3
  • 17
  • 41
3

One easy to understand solution is the following:

int maxHighScore = playerList.stream()
                             .map(player -> player.getHandScore())
                             .max()
                             .orElse(-1);

List<Player> highestHandScores = playerList.stream()
                                           .filter(player -> player.getHandScore() == maxHighScore)
                                           .collect(Collectors.toList());

In the first step we get the maxHighScore and in the second step we filter the players to keep only those with the maximum score.

Ayoub Rossi
  • 410
  • 5
  • 18
1
    int max = playerList.stream()
            .max(Comparator.comparing(Player::getHandScore))
            .get()
            .getHandScore();

    List<Player> playerLists = playerList
            .stream()
            .filter(m -> m.getHandScore() == max)
            .collect(Collectors.toList());

Avinash Gupta
  • 208
  • 5
  • 18
1

The problem I have with some of the other answers is that they traverse the list of players twice – one for grouping by player score, and one for getting the players with the highest score. This may be trivial in normal cases, but might be a problem when the list of players is larger.

One thing to do in order to avoid traversing the list twice, is using a SortedMap. Once we have grouped the players by their score, we can simply call lastKey() to get the highest key at once:

SortedMap<Integer, List<Player>> topPlayers = playerList.stream()
    .collect(Collectors.groupingBy(Player::getScore, TreeMap::new, Collectors.toList()));
topPlayers.get(topPlayers.lastKey());

Or, as Holger says in the comments, if you use NavigableMap, you can save another map lookup:

NavigableMap<Integer, List<Player>> topPlayers = playerList.stream()
    .collect(Collectors.groupingBy(Player::getScore, TreeMap::new, Collectors.toList()));
topPlayers.lastEntry().getValue();

But still, the answer given by Stuart Marks to the linked post is, in my opinion, better, since not all elements are stored (grouped into buckets), but instead, they are immediately dropped if they are found to not belong to the maximums.

This potentially saves memory.

MC Emperor
  • 22,334
  • 15
  • 80
  • 130
  • 2
    `topPlayers.get(topPlayers.lastKey());` performs an unnecessary map lookup. When you change the type of `topPlayers` to `NavigableMap>`, you can use `topPlayers.lastEntry().getValue();` instead. – Holger Jul 04 '19 at 14:23
1

My answer will be a version of MC Emperor's anwer with a SortedMap, but this version will discard the "lower" groups as soon as it finds a single Player whose score is higher. This version is for when there's really a lot of elements, where keeping all seen elements can become a problem. For example, when streamed items are read from a really large file, that won't fit into memory.

The solution as a whole would look like this:

List<Player> topScore = playersStream().collect(
    topGroup(Comparator.comparingInt(Player::getHandScore))
);

To get it working, you will need a custom collector with a stateful container to keep the groups. I'm not sure there is something like that in JDK (not in 8 in any case), but you might be able to find it in one of the libraries out there. The outward facing metod would look like this:

static <T> Collector<T, ?, List<T>> topGroup(final Comparator<? super T> comparator) {
    Objects.requireNonNull(comparator, "comparator");
    return Collector.of(
        () -> new Group<>(comparator),
        // My local compiler can't infer type properly, I had to help it.
        // Your experience may be different
        (BiConsumer<Group<T>, T>) Group::accept,
        Group::merge,
        Group::asList
   );
}

And the most vital part is the stateful Group<T>. It's purpose is to be the container of elements which the external comparator considers ordered the highest. As soon as higher ordered element encountered, the group discards all its previous contents. Example implementation is:

private static class Group<T> {
    private final Comparator<? super T> comparator;
    T sample;
    List<T> more;

    public Group(Comparator<? super T> comparator) {
        this.comparator = comparator;
    }

    public void accept(T el) {
        if (sample == null) {
            sample = el;
        }
        else {
            int order = comparator.compare(sample, el);
            if (order == 0) {
                more().add(el);
            }
            else if (order > 0) {
                // element of a higher order, discard everything and make it a sample
                sample = el;
                more = null;
            }
            // else {element of a lower order, ignore}
        }
    }

    public Group<T> merge(Group<T> other) {
        if (this.comparator != other.comparator) {
            throw new IllegalArgumentException("Cannot merge groups with different orders");
        }
        if (sample == null) {
            return other; // we're empty
        }
        int order = comparator.compare(this.sample, other.sample);
        if (order >= 0) {
            if (order == 0) {
                // merge with other group
                more().addAll(other.asList());
            }
            return this;
        }
        else {
            // other group is higher than us
            return other;
        }
    }

    public List<T> asList() {
        List<T> result = new ArrayList<>();
        if (sample != null) {
            result.add(sample);
        }
        if (more != null) {
            result.addAll(more);
        }
        return result;
    }
}

This implementation is also a gateway in solving a problem of finding "Top N with ties" (where my implementation is the "Top 1 with ties").

M. Prokhorov
  • 3,894
  • 25
  • 39