Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grouping and summing a list into a map using Lambda

I have a list of MoveTrack objects that i want to group by Month and count and store the data in a Map<String, Double>. I am trying to learn Lambda and experiment with it so trying to do this task with Lambda.

class MoveTrack {
private Date time;
private Double movementAmount;

//getters and setters
}

// in my main method
List<MoveTrack> mveTracking = new ArrayList<>();
Map<String, Double> movMap = new HashMap<>();

So I want to be able to group the moveTracking list and sum the values into the movMap, with each Map Key = Month Name (January, February, etc.) and corresponding Value the movement amount which is a sum of doubles for that month.

like image 269
Aeseir Avatar asked Feb 21 '16 07:02

Aeseir


3 Answers

Collectors.groupingBy Allows you to reduce a list to a map where the key is some manipulation of the list's element and the value is some aggregate function applied to a manipulation of the element. In your case, you could use something like this:

SimpleDateFormat monthFormatter = new SimpleDateFormat("MMM");
Map<String, Double> moveMap = 
    moveTracking.stream()
                .collect(Collectors.groupingBy
                    (m -> monthFormatter.format(m.getTime()),
                     Collectors.summingDouble(MoveTrack::getMovementAmount)));
like image 108
Mureinik Avatar answered Oct 16 '22 05:10

Mureinik


If you're open to using a third-party library, Eclipse Collections has specialized methods called sumBy that work well with lambdas.

If you use MutableList from Eclipse Collections for the moveTracking list, the code would look as follows:

MutableList<MoveTrack> moveTracking = Lists.mutable.empty();
ObjectDoubleMap<String> doubleMap =
    moveTracking.sumByDouble(MoveTrack::getMonthName, MoveTrack::getMovementAmount);

It's up to you to determine where and how you want to implement getMonthName(). You'll notice that sumByDouble returns an ObjectDoubleMap. This means you can use double for your movementAmount and it will not be boxed during the summing.

If you want to keep your List<MoveTrack> type as it currently is, you can either use a ListAdapter or the Iterate utility class to accomplish the same thing.

List<MoveTrack> list = ...;
ObjectDoubleMap<String> doubleMap =
    Iterate.sumByDouble(list, MoveTrack::getMonthName, MoveTrack::getMovementAmount);

Note: I am a committer for Eclipse Collections

like image 6
Donald Raab Avatar answered Oct 16 '22 05:10

Donald Raab


Use Collectors.groupingBy as follows:

mveTracking.stream().collect(Collectors.groupingBy(m -> {
            final Calendar cal = Calendar.getInstance();
            cal.setTime(m.getTime());
            final int month = cal.get(Calendar.MONTH);
            return new DateFormatSymbols().getMonths()[month];
        }, Collectors.summingDouble(MoveTrack::getMovementAmount)));

Code for you ;)

import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupList {
    public static void main(String[] args) {
        List<MoveTrack> mveTracking = new ArrayList<>();
        mveTracking.add(new MoveTrack(new Date(), 10.0d));
        mveTracking.add(new MoveTrack(new Date(), 11.0d));
        Map<String, Double> movMap = new HashMap<>();
        movMap = mveTracking.stream().collect(Collectors.groupingBy(m -> {
            final Calendar cal = Calendar.getInstance();
            cal.setTime(m.getTime());
            final int month = cal.get(Calendar.MONTH);
            return new DateFormatSymbols().getMonths()[month];
        }, Collectors.summingDouble(MoveTrack::getMovementAmount)));
        System.out.println(movMap);
    }
}

final class MoveTrack {

    private final Date time;
    private final Double movementAmount;

    public MoveTrack(final Date time, final Double movementAmount) {
        this.time = new Date(time.getTime());
        this.movementAmount = movementAmount;
    }

    public Date getTime() {
        return time;
    }

    public Double getMovementAmount() {
        return movementAmount;
    }
}
like image 2
Arpit Aggarwal Avatar answered Oct 16 '22 05:10

Arpit Aggarwal