Reputation: 551
I would like to get the highest score group by Id .If two highest score's are same then i would like get the highest score based on lowest Optional ID.I would like to get it in Java Stream.So far i am trying the following codes which is not working Example :
Array List:
ID: 1 Score:80 OptionalId:1
ID: 1 Score:90 OptionalId:2
ID: 1 Score:90 OptionalId:3
ID: 2 Score:80 OptionalId:1
ID: 2 Score:100 OptionalId:3
ID: 2 Score:100 OptionalId:5
The result should be
ID: 1 Score 90 OptionalId:2
ID 2 Score 100 OptionalId:3
Map<Long, Optional<Person>> result1 = records.stream()
.collect(Collectors.groupingBy(Person::getId,
Collectors.maxBy(Comparator.comparing(Person::getScore)),
Collector.minBy(Comparator.comparing(Person::getOptionalId))));
for(Person ns: result1) {
sb.append(ns.getBatchNumber());
sb.append(',');
Upvotes: 2
Views: 1780
Reputation: 21124
For a given Id value, there must be a Person. Existence of the Id value solely depends on the Person. So if there exists an Id, there must be a Person too. Therefore what is the point of having an Optional<Person>
as the value of the map. In contrast, it makes more sense to merely have a Person
instance as the value in the map
. Here I am using the toMap
collector in concert with the BinaryOperator.maxBy
to get the work done. Here's how it looks. Notice how the BinaryOperator.maxBy
is used as the mergeFunction
.
Map<Integer, Person> maxPersonById = records.stream()
.collect(Collectors.toMap(Person::getId, Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(Person::getScore)
.thenComparing(Comparator.comparing(Person::getOptionalId).reversed()))));
And here's the output for the above given input.
{1=Person [id=1, score=90, optionalId=2], 2=Person [id=2, score=100, optionalId=3]}
Upvotes: 2
Reputation: 44368
I suggest you start with a custom Comparator<Person>
which prioritizes a max of score
and then a min of optionalId
. It's a good way to pass it to a variable for sake of brevity:
final Comparator<Person> comparator = Comparator
.comparing(Person::getScore) // descending score first
.thenComparing(Comparator // then ..
.comparing(Person::getOptionalId) // .. optionalId
.reversed()); // .. but ascending
Now use some collectors using java-stream.
Collectors::groupingBy
to group all the Person
s by the id
and downstream the valueCollectors::reducing
to reduce all the Person
s with the same id
into one using the Comparator<Person>
to get the one with the highest score
and lowest optionalId
.Collectors::collectingAndThen
to flatten the structure from Collection<Optional<Person>>
to Collection<Person>
since any reducing operation results as Optional
- this step is optional and might be skipped according to your sample.Here is the code:
Collection<Person> filtered = records.stream() // Stream<Person>
.collect(Collectors.groupingBy( // from Map<Long, List<Person>>
Person::getId,
Collectors.collectingAndThen( // .. downstream to ..
Collectors.reducing((a, b) -> // .. Map<Long, Optional<Person>>
comparator.compare(a, b) > 0 ? a : b),
Optional::get)) // .. Map<Long, Person>
.values(); // .. Collection<Person>
[Person [id=1, score=90, optionalId=2], Person [id=2, score=100, optionalId=3]]
Upvotes: 3
Reputation: 520958
You may try the following stream code which aggregates by ID
, and then finds the max score using a two level sort, first by score, then, by optional ID in case of a tie for score:
import static java.util.Collections.reverseOrder;
import static java.util.Comparator.comparing;
Map<Long, Optional<Person>> result1 = records.stream()
.collect(Collectors.groupingBy(Person::getId,
Collectors.maxBy(
Comparator.comparing(Person::getScore)
.thenComparing(reverseOrder(comparing(Person::getOptionalId))))));
Optional[ID: 1 Score: 90 OptionalId: 2]
Optional[ID: 2 Score: 100 OptionalId: 3]
The trick here is to reverse the sorting order for only the optional ID, which we want to be ascending, not descending. The sort order by default would be descending, because we are invoking Collections.maxBy
.
I cite this great SO question for help with the reverse syntax. Also, I borrowed the boiler plate code from @mayamar to setup the following demo:
(demo only for demonstration purposes)
Upvotes: 1
Reputation: 412
I changed a bit and introduced a helper class that compares score and optionalId.
public class T21Group {
public static void main(String[] args) {
List<Person> records = new ArrayList<>();
records.add(new Person(1, 80, 1));
records.add(new Person(1, 90, 2));
records.add(new Person(1, 90, 3));
records.add(new Person(2, 80, 1));
records.add(new Person(2, 100, 3));
records.add(new Person(2, 100, 5));
Map<Long, Optional<Person>> result1 = records.stream()
.collect(Collectors.groupingBy(Person::getId, Collectors.maxBy(Comparator.comparing(Pair::new))));
for (Optional<Person> ns : result1.values()) {
System.out.println(ns);
}
}
public static class Pair implements Comparable<Pair> {
long score;
long optionalId;
public Pair(Person p) {
score = p.getScore();
optionalId = p.getOptionalId();
}
@Override
public int compareTo(Pair o) {
if (this.score == o.score) {
return Long.compare(o.optionalId, this.optionalId);
}
return Long.compare(this.score, o.score);
}
}
public static class Person {
private long id;
private long score;
private long optionalId;
public Person(long id, long score, long optionalId) {
this.id = id;
this.score = score;
this.optionalId = optionalId;
}
@Override
public String toString() {
return "ID: " + id + " Score: " + score + " OptionalId: " + optionalId;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getScore() {
return score;
}
public void setScore(long score) {
this.score = score;
}
public long getOptionalId() {
return optionalId;
}
public void setOptionalId(long optionalId) {
this.optionalId = optionalId;
}
}
}
Upvotes: 0