IncompleteCoder
IncompleteCoder

Reputation: 153

java8 stream grouping aggregate

Given a java class Something

class Something {
     String parent;
     String parentName;
     String child;
     Date at;
     int noThings;

     Something(String parent, String parentName, String child, Date at, int noThings) {
          this.parent = parent;
          this.parentName = parentName;
          this.child = child;
          this.at = at;
          this.noThings = noThings;
      }

      String getParent() { return parent; }
      String getChild() { return child; }
      int getNoThings() { return noThings; }
}

I have a list of something objects,

List<Something> hrlySomethings = Arrays.asList(
    new Something("parent1", "pname1", "child1", new Date("01-May-2015 10:00:00"), 4),
    new Something("parent1", "pname1", "child1", new Date("01-May-2015 12:00:00"), 2),
    new Something("parent1", "pname1", "child1", new Date("01-May-2015 17:00:00"), 8),
    new Something("parent1", "pname1", "child2", new Date("01-May-2015 07:00:00"), 12),
    new Something("parent1", "pname1", "child2", new Date("01-May-2015 17:00:00"), 14),
    new Something("parent2", "pname2", "child3", new Date("01-May-2015 11:00:00"), 3),
    new Something("parent2", "pname2", "child3", new Date("01-May-2015 16:00:00"), 2));

I want to group the objects by parent and children and then find the total/sum of the "noThings" field for the last 24 hours.

List<Something> dailySomethings = Arrays.asList(
    new Something("parent1", "pname1", "child1", new Date("01-May-2015 00:00:00"), 14),
    new Something("parent1", "pname1", "child2", new Date("01-May-2015 00:00:00"), 26),
    new Something("parent2", "pname2", "child3", new Date("01-May-2015 00:00:00"), 5))

I'm trying to use the streams to do this

I can figure out how to use the grouping to get a map of maps, and the total

    Map<String,Map<String,IntSummaryStatistics>> daily =
        hrlySomethings.stream().collect(
    Collectors.groupingBy(Something ::getParent, 
    Collectors.groupingBy(ClientCollectionsReceived::getChild,
    Collectors.summarizingInt(ClientCollectionsReceived::getNoThings))));

I can figure out how to get a distinct list based on parent and child,

    Date startHour = "01-May-2015 00:00:00";
    int totalNoThings = 0; // don't know how to put sum in here
    List<Something> newList 
      = hrlySomethings.stream()
            .map((Something other) -> {
                    return new Something(other.getParent(),
                    other.getChild(), startHour, totalNoThings);
                })
            .distinct()
            .collect(Collectors.toList());

But I don't know how to combine the two to get the distinct list, with the totals. Is this possible?

Upvotes: 2

Views: 1421

Answers (1)

Tagir Valeev
Tagir Valeev

Reputation: 100169

First I assume that you are using java.util.Date (though I'd advise you to move to new java.time API). Second, I assume that Something class has also properly implemented equals and hashCode. Also more getters are necessary:

String getParentName() { return parentName; }
Date getAt() { return at; }

Under these assumptions your task can be solved like this:

List<Something> dailySomethings = hrlySomethings.stream().collect(
    Collectors.groupingBy(
        smth -> new Something(smth.getParent(), 
                              smth.getParentName(), 
                              smth.getChild(), 
                              new Date(smth.getAt().getYear(),
                                       smth.getAt().getMonth(), 
                                       smth.getAt().getDate()), 
                              0),
        Collectors.summingInt(Something::getNoThings)
    )).entrySet().stream()
                 .map(entry -> new Something(entry.getKey().getParent(),
                                             entry.getKey().getParentName(), 
                                             entry.getKey().getChild(), 
                                             entry.getKey().getAt(), 
                                             entry.getValue()))
                 .collect(Collectors.toList());

We use groupingBy only once, but create a suitable grouping key, which is the Something with parent, parentName and child set to the original, at changed to the day beginning and noThings set to zero. This way you group what you want. If you need only total sums, then summarizingInt is unnecessary, summingInt is enough. After that we transform the resulting map to the list creating new Something objects where noThings is filled from the map values and the rest is filled from the keys.

Upvotes: 2

Related Questions