How do I group a list inside an object by two of the object attributes?
I'm using Drools 7.9.0 and Java 8. I have a Result
class that is used as return to each matched Drools rule.
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
public class Result implements Serializable {
private Integer id;
private String name;
private List<Occurrences> occurrences = new ArrayList<>();
public void addOccurrence(Occurrences occurrence) {
this.occurrences.add(occurrence);
}
}
After Drools execution, I end up with a List<Result>
. Converted to JSON it looks like this.
[
{
name: "Whatever"
id: 0001,
occurrences: [{
(...)
}]
},
{
name: "Whatever"
id: 0001,
occurrences: [{
(...)
}]
},
{
name: "Whatever"
id: 0002,
occurrences: [{
(...)
}]
},
{
name: "Other thing"
id: 0002,
occurrences: [{
(...)
}]
}
]
I need to group up my list of Result
s so the ocurrences
are grouped by id
and name
, like this.
[
{
name: "Whatever"
id: 0001,
occurrences: [{
(...)
}, {
(...)
}]
},
{
name: "Whatever"
id: 0002,
occurrences: [{
(...)
}]
},
{
name: "Other thing"
id: 0002,
occurrences: [{
(...)
}]
}
]
What would be the best way to implement this? I have two options:
List<Result>
is already structured the way I need. Maybe create a class with a addResult
method that checks the list for id
and name
and add the occurrence to the right entry. But this is not ideal, because it will increase complexity inside the rules. List<Result>
so it groups the occurrences
by id
and name
. But I have no idea how to do this in an optimized and simple way. What's the best way to do the 2nd option?
You may do it like so,
Map<String, List<Result>> resultsWithSameIdAndName = results.stream()
.collect(Collectors.groupingBy(r -> r.getId() + ":" + r.getName()));
List<Result> mergedResults = resultsWithSameIdAndName.entrySet().stream()
.map(e -> new Result(Integer.valueOf(e.getKey().split(":")[0]),
e.getKey().split(":")[1],
e.getValue().stream().flatMap(r -> r.getOccurrences().stream())
.collect(Collectors.toList())))
.collect(Collectors.toList());
A simple way of the 2nd option could be:
Map<String, List<Occurences>> resultsMapped = new HashMap<>();
List<Result> collect = results.forEach(r -> resultsMapped.merge(String.format("%s%d", r.name, r.id),
r.occurrences, (currentOccurences, newOccurences) -> {
currentOccurences.addAll(newOccurences);
return currentOccurences;
});
Which store in a map your occurences, the key being the name and the id concatenated in a string. This key is not optimized.
You could also use some streams for that. This will group by ID and name, and then create a new Result object for each group, containing all occurrences of the group.
Collection<Result> processed = results.stream().collect(Collectors.groupingBy(r -> r.getId() + r.getName(),
Collectors.collectingAndThen(Collectors.toList(),
l -> new Result(l.get(0).getId(), l.get(0).getName(),
l.stream().flatMap(c -> c.getOccurrences().stream()).collect(Collectors.toList()))
))).values();
This requires a AllArgsConstructor to be added to the Result class, as well as some more getters, but you can adjust this as you like. The key used for grouping is not safe in this example.
I would add the following method to Result
:
Result addOccurrences (List<Occurrences> occurrences) {
this.occurrences.addAll (occurrences);
return this;
}
And then you can use Streams:
List<Result> collect = results.stream ()
.collect (Collectors.groupingBy (Result::getId, Collectors.groupingBy (Result::getName, Collectors.toList ())))
.values ()
.stream ()
.map (Map::values)
.flatMap (Collection::stream)
.map (Collection::stream)
.map (resultStream -> resultStream.reduce ((r0, r1) -> r0.addOccurrences (r1.getOccurrences ())))
.filter (Optional::isPresent)
.map (Optional::get)
.collect (Collectors.toList ());
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.