简体   繁体   中英

How to calculate totals of two subgroups using Java 8 Streams with maps and lists

I have a Car POJO:

class Car
{
  String make;
  int year;
  BigDecimal amount;
}

And a List<Car> of cars such as that:

make  : year : price
honda : 2011 : $20,000
honda : 2011 : $30,000
honda : 2012 : $50,000
ford  : 2012 : $12,000

Where I want to have as a result:

// Map<make, List<year, totalForYear>>
Map<String, Map<int, BigDecimal>> revenuesByMakeAndYear;

How can I achieve this with Streams? So far I'm halfway with:

// Map<make, List<car>>
Map<String, List<Car>> carsByMake = cars.stream()
        .collect(Collectors.toMap(
            car -> car.getMake(),
            car -> cars.stream()
                .filter(subCar -> subCar.getMake().equals(car.getMake())
                .collect(Collectors.toList())));

But how do I get to the next step?

You can use nested groupingBy to group by make and year, and then reducing to produce the total revenue):

Map<String,Map<Integer,BigDecimal>> totals = 
    cars.stream ()
        .collect (Collectors.groupingBy (c->c.make,
                                         Collectors.groupingBy (c->c.year,
                                                                Collectors.reducing (new BigDecimal(0),
                                                                                     c->c.amount,
                                                                                     BigDecimal::add))));

If the property you wish to sum wasn't a BigDecimal , you could have replaced reducing with the simpler summingInt or summingLong or summingDouble .

Of course, assuming your Car class has getters, you should replace c->c.make with Car::getMake , etc...

Map<String,Map<Integer,BigDecimal>> totals = 
    cars.stream ()
        .collect (Collectors.groupingBy (Car::getMake,
                                         Collectors.groupingBy (Car::getYear,
                                                                Collectors.reducing (new BigDecimal(0),
                                                                                     Car::getAmount,
                                                                                     BigDecimal::add))));

I ran this on your sample input and got:

{honda={2011=50000, 2012=50000}, ford={2012=12000}}

You can use groupingBy with Map and specify what to do with duplicate keys (ie add the amount to generate aggregate result). Try the following:

List<Car> cars = new ArrayList<>(); //Your list
Map<String, Map<Integer, BigDecimal>> sum = cars.stream()
        .collect(Collectors.groupingBy(c -> c.getMake(), 
                Collectors.toMap(c -> c.getYear(), c -> c.getAmount(), 
                        (c1, c2) -> c1.add(c2))));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM