Reputation: 1621
I have an Topic:
@Entity
public class Topic {
@Id
private int id;
private LocalDate date;
private String name;
private int points;
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
I'm getting list of topics in given date by spring data jpa method:
List topics = topicRepository.findByDateBetween(begin, end); which on output has e.g:
Topic(id=1, date="2018-01-01", name="Java examples", User(...), 12)
Topic(id=2, date="2018-02-02", name="Java examples", User(...), 34)
Topic(id=3, date="2018-02-02", name="Java examples", User(...), 56)
Topic(id=4, date="2018-03-03", name="Java examples", User(...), 78)
Topic(id=5, date="2018-03-03", name="Java examples", User(...), 90)
What I try to achive is to filter my result output as (if date && User is the same add points)
Topic(id=1, date="2018-01-01", name="Java examples", User(...), 12)
Topic(id=2, date="2018-02-02", name="Java examples", User(...), 90)
Topic(id=4, date="2018-03-03", name="Java examples", User(...), 168)
My actual solution returns map with date as key and summed points as value, but I need to have more data given in output like User or name.
return topics.stream()
.collect(Collectors.groupingBy(Topic::getDate,
Collectors.summingInt(Topic::getPoints)));
Maybe there is another way to instead of map return created dto for that case? e.g.
@Data
public class ResultDto {
private LocalDate date;
private String name;
private int points;
private User user;
}
Upvotes: 3
Views: 1087
Reputation: 28133
A simple way to group by a subset of fields is to use a TreeMap
with a custom Comparator
. Suppose you were to define
Comparator<Topic> byDateAndUser = Comparator.comparing(Topic::getDate)
.thenComparing(t -> t.getUser().getUserId());
Map<Topic,...> map = new TreeMap<>(byDateAndUser);
The resulting map would use the supplied comparator instead of equals
to determine equality and will thus treat all topics with the same date and user as the same.
This feature of TreeMap
allows you to compute a Map of topic to the total points and it will only contain one entry for each combination of date/user:
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;
Comparator<Topic> byDateAndUser = Comparator.comparing(Topic::getDate)
.thenComparing(t -> t.getUser().getUserId());
Map<Topic, Integer> pointTotals = topics.stream()
.collect(groupingBy(
topic -> topic,
() -> new TreeMap<>(byDateAndUser),
summingInt(Topic::getPoints)
));
Upvotes: 2
Reputation: 34460
Assuming User
implements hashCode
and equals
consistently, you could group to a map of date/user composite key and ResultDto
values. To do this, you need two operations: one to map to
and the other one to aggregate points for each group. (You can place these operations either in ResultDto
or in a utility class, etc, here I'm assuming the first one is in a Mapper
utility class and the 2nd one in ResultDto
):
public final class Mapper {
private Mapper() { }
public static ResultDto fromTopic(Topic topic) {
ResultDto result = new ResultDto();
result.setDate(topic.getDate());
result.setName(topic.getName());
result.setPoints(topic.getPoints());
result.setUser(topic.getUser());
return result;
}
}
In ResultDto
:
public ResultDto merge(ResultDto another) {
this.points += another.points;
return this;
}
Note that I'm assigning the first Topic.name
found in the Mapper.fromTopic
mapping operation. I'm assuming this is an inconsistency in your example, please take it into account if you use this approach in a real world scenario.
Now we can stream the topics and group by date/user:
Map<List<Object>, ResultDto> groups = topics.stream()
.collect(Collectors.toMap(
topic -> Arrays.asList(topic.getDate, topic.getUser()), // Java 9: List.of
Mapper::fromTopic,
ResultDto::merge));
This collects to a map whose key is a List
with its first element being the date and the second, the user. These 2-element lists aren't very useful for later usage, here we're only using them to create a composite key and group topics by this key. The values of the map is a ResultDto
instance that is initially created from the topic and then merged with other topics that belong to the same group (same date and user). In this ResultDto.merge
operation the points
are being summed.
The results you need are the values of the map:
Collection<ResultDto> results = groups.values(); // or new ArrayList<>(groups.values())
EDIT: Here's a slightly more succinct variant without streams:
Map<List<Object>, ResultDto> groups = new HashMap<>();
topics.forEach(topic -> groups.merge(
List.of(topic.getDate(), topic.getUser()), // Java 8: Arrays.asList
Mapper.fromTopic(topic),
ResultDto::merge));
Collection<ResultDto> results = groups.values();
Upvotes: 1