Shek87
Shek87

Reputation: 23

java 8 stream: grouping by and storing sum in new object, and merge maps

I have a class Row such as:

class Row {
   public Long id1;
   public String id2;
   public Long metric1;
   public Long metric2;

   public Stats getStats() {
      return new Stats(metric1, metric2);
   }
}

and a class Stats:

class Stats{
    public Long totalMetric1;
    public Long totalMetric2;

    public void addMetric1(Long metric1) {
       this.totalMetric1 = this.totalMetric1 + metric1;
    }

    public void addMetric2(Long metric2) {
       this.totalMetric2 = this.totalMetric2 + metric2;
    }
}

I have a list of rows

List<Row> rowList;

and i need to convert it into a map grouped by id1 and id2, and i need the metric data to be summed up into Stats object in this form

Map<Long, Map<String, Stats>>

I am using java stream to generate this but stuck at this point:

Map<Long, Map<String, List<Stats>>> map = stream.collect(
            Collectors.groupingBy(r->r.id1(), 
            Collectors.groupingBy(r->r.id2,
            Collectors.mapping(r->r.getStats(), Collectors.toList()))));

How do i convert the list into another object having sum of all the objects in that list?

Also is there a way to merge two output maps of above required form into a third one using java stream?

Example:-

Input: A list of Rows

<1,"ABC", 1, 2>
<1,"ABC", 2, 2>
<1,"XYZ", 1, 2>
<2,"ABC", 1, 2>
<2,"XYZ", 1, 2>
<3,"XYZ", 1, 0>
<3,"XYZ", 2, 1>
<3,"XYZ", 2, 3>

Result: Map grouped by Field 1, Field 2, with sum of Field 3 and Field 4

1 - ABC - 3,4
    XYZ - 1,2
2 - ABC - 1,2
    XYZ - 1,2
3 - XYZ - 5,4

Upvotes: 2

Views: 2736

Answers (1)

Artur Biesiadowski
Artur Biesiadowski

Reputation: 3698

My suggestion would be to do it in bit easier way than nested collections. in Row class, add

public Pair<Long,String> getIds() {
   return new Pair<>(id1,id2);
}

in Stats class, add

public Stats merge(Stats other) {
    return new Stats(totalMetric1+other.totalMetric1, totalMetric2 + other.totalMetric2);
}

and then write something like

      Map<Pair<Long, String>, Stats> stats = rowList.stream().
              collect(Collectors.toMap(Row::getIds,Row::getStats, (s1,s2) -> s1.merge(s2)));

If you are not allergic to guava (and you shouldn't be, this is one of no-brainer libraries to include in every project, at least for me), you can write it in more elegant and readable

      Table<Long, String, Stats> table = rowList.stream().
            collect(Tables.toTable(Row::getId1, Row::getId2, Row::getStats,(s1,s2) -> s1.merge(s2),HashBasedTable::create));

without having to use Pair<> or nested maps.

Upvotes: 1

Related Questions