Getting the sum and max from a list using stream

2019-07-27 12:20发布

问题:

Hi I have a List where the data looks like this

[{"month":"April","day":"Friday","count":5},

{"month":"April","day":"Monday","count":6},

{"month":"April","day":"Saturday","count":2},

{"month":"April","day":"Sunday","count":1},

{"month":"April","day":"Thursday","count":7},

{"month":"April","day":"Tuesday","count":8},

{"month":"April","day":"Wednesday","count":10},

{"month":"March","day":"Friday","count":3},

{"month":"March","day":"Monday","count":2},

{"month":"March","day":"Saturday","count":15},

{"month":"March","day":"Sunday","count":11},

{"month":"March","day":"Thursday","count":4},

{"month":"March","day":"Tuesday","count":20},

{"month":"March","day":"Wednesday","count":7},

{"month":"May","day":"Friday","count":2},

{"month":"May","day":"Monday","count":0},

{"month":"May","day":"Saturday","count":7},

{"month":"May","day":"Sunday","count":4},

{"month":"May","day":"Thursday","count":8},

{"month":"May","day":"Tuesday","count":3},

{"month":"May","day":"Wednesday","count":6}]

My object class is

String month;
String day;
Integer count;

What I want to get by using stream is sum of count grouped by month and the day with max count for that month.

so end result will look something like

April, Wednesday, 39 March, Tuesday, 62 May, Thursday , 30

I have been trying to use stream and grouping by but no luck. Any help is appreciated. Thanks

EDIT

 Map<String, Integer> totalMap = transactions.stream().collect(Collectors.groupingBy(MonthlyTransaction::getMonth, Collectors.summingInt(MonthlyTransaction::getCount)));
     Map<String, String> maxMap = transactions.stream().collect(Collectors.groupingBy(MonthlyTransaction::getMonth)).values().stream().toMap(Object::getDay, Collextions.max(Object::getCount);

obviously the maxMap method is wrong but I do not know how to write it.

回答1:

If you want to find both the sum of counts per month and the day with the max count per month in a single pass, I think you need a custom collector.

First, let's create a holder class where to store the results:

public class Statistics {

    private final String dayWithMaxCount;

    private final long totalCount;

    public Statistics(String dayWithMaxCount, long totalCount) {
        this.dayWithMaxCount = dayWithMaxCount;
        this.totalCount = totalCount;
    }

    // TODO getters and toString
}

Then, create this method, which returns a collector that accumulates both the sum of counts and the max count, along with the day in which that max was found:

public static Collector<MonthlyTransaction, ?, Statistics> withStatistics() {
    class Acc {
        long sum = 0;
        long maxCount = Long.MIN_VALUE;
        String dayWithMaxCount;

        void accumulate(MonthlyTransaction transaction) {
            sum += transaction.getCount();
            if (transaction.getCount() > maxCount) {
                maxCount = transaction.getCount();
                dayWithMaxCount = transaction.getDay();
            }
        }

        Acc merge(Acc another) {
            sum += another.sum;
            if (another.maxCount > maxCount) {
                maxCount = another.maxCount;
                dayWithMaxCount = another.dayWithMaxCount;
            }
            return this;
        }

        Statistics finish() {
            return new Statistics(dayWithMaxCount, sum);
        }
    }
    return Collector.of(Acc::new, Acc::accumulate, Acc::merge, Acc::finish);
}

This uses the local class Acc to accumulate and merge partial results. The finish method returns an instance of the Statistics class, which holds the final results. At the end, I'm using Collector.of to create a collector based on the methods of the Acc class.

Finally, you can use the method and class defined above as follows:

Map<String, Statistics> statisticsByMonth = transactions.stream()
    .collect(Collectors.groupingBy(MonthlyTransaction::getMonth, withStatistics()));


回答2:

did this in 2 steps instead of trying to write 1 stream to achieve the result

//First get the total of counts grouping by month
Map<String, Integer> totalMap = transactions.stream()
  .collect(Collectors.groupingBy(MonthlyTransaction::getMonth, Collectors.summingInt(MonthlyTransaction::getCount)));

List<MonthlyTransaction> finalStat = new ArrayList<>();

//iterate over the total count map
totalMap.entrySet().stream().forEach(entry -> {
//Using the Stream filter to mimic a group by 
MonthlyTransaction maxStat = transactions.stream()
    .filter(t -> t.getMonth().equals(entry.getKey()))
        //getting the item with the max count for the month
       .max(Comparator.comparing(MonthlyTransaction::getCount)).get();

//Setting the count to the total value from the map as the max count value is not a requirement.
maxStat.setCount(entry.getValue());

//add the item to the list
finalStat.add(maxStat);
});

This may not be the best approach to the problem but this gives me the exact result. Thanks to everyone who had a look at it and tried to help.