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").