Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group by two fields using Collectors

I have a java object Record:

public Record(ZonedDateTime day, int ptid, String name, String category, int amount) {
        this.day= day;
        this.id= id;
        this.name = name;
        this.category = category;
        this.amount = amount;
}

I'm grouping a list of Records by their day, and then creating a new Record which combines the amount field and returns a map:

Map<ZonedDateTime, Record> map = tempList.stream().collect(Collectors.groupingBy(Record::getDay,
                Collectors.collectingAndThen(
                        Collectors.reducing((r1, r2) -> new Record(r1.getDay(),Integer.toString(r1.getId),r1.getName(),
                                r1.getCategory(),r1.getAmount() + r2.getAmount())),
                        Optional::get)));

I want to group the list by day AND category. So if day and category are the same, I would like to combine the amount field in a new Record like I am already doing. I need to add in another Collectors.groupingBy clause but the syntax just hasn't been working. I believe the return type would be Map<ZonedDateTime, Map<String, List<Record>>>. I also then need to convert the returned map into a List.

I've been trying to go off of this example Group by multiple field names in java 8

like image 885
Drew13 Avatar asked Mar 22 '26 02:03

Drew13


1 Answers

You could simplify the whole construct by using Collectors.toMap:

Map<List<Object>, Record> map = tempList.stream()
        .collect(Collectors.toMap(
                            r -> List.of(r.getDay(), r.getCategory()), // or Arrays.asList
                            Record::new,
                            Record::merge));

The trick is to group by a composite key. In this case, we're using a List<Object> with both Record.day and Record.category. (List implements Object.hashCode and Object.equals as required, so it can be safely used as the key of any Map).

For the reduction to work, we need a copy constructor and a merge method:

public Record(Record r) {
    this(r.day, r.name, r.name, r.category, r.amount);
}

public Record merge(Record r) {
    this.amount += r.amount;
    return this;
}

Finally, to return the list of records, there's no need to do anything fancier than this:

List<Record> result = new ArrayList<>(map.values());
like image 145
fps Avatar answered Mar 23 '26 23:03

fps



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!