15

I have a list of taxes:

TaxLine = title:"New York Tax", rate:0.20, price:20.00
TaxLine = title:"New York Tax", rate:0.20, price:20.00
TaxLine = title:"County Tax", rate:0.10, price:10.00

TaxLine class is

public class TaxLine {
    private BigDecimal price;
    private BigDecimal rate;
    private String title;
}

I would like to combine them base on unique title and rate, then add the price, expected:

 TaxLine = title:"New York Tax", rate:0.20, price:40.00
 TaxLine = title:"County Tax", rate:0.10, price:10.00

How can I do this in Java 8?

Group by multiple field names in java 8, is not summing a field, it can only group by the two fields.

Community
  • 1
  • 1
richersoon
  • 4,682
  • 13
  • 44
  • 74

3 Answers3

24

The principle is the same as in the linked question, you just need a different downstream collector to sum:

List<TaxLine> flattened = taxes.stream()
    .collect(Collectors.groupingBy(
        TaxLine::getTitle,
        Collectors.groupingBy(
            TaxLine::getRate,
            Collectors.reducing(
                BigDecimal.ZERO,
                TaxLine::getPrice,
                BigDecimal::add))))
    .entrySet()
    .stream()
    .flatMap(e1 -> e1.getValue()
         .entrySet()
         .stream()
         .map(e2 -> new TaxLine(e2.getValue(), e2.getKey(), e1.getKey())))
    .collect(Collectors.toList());
shmosel
  • 49,289
  • 6
  • 73
  • 138
2

One way is to create an object for the set of fields you're grouping by. That class can be built to provide nice helper methods too.

So, with your original class completed like this:

public final class TaxLine {
    private String title;
    private BigDecimal rate;
    private BigDecimal price;
    public TaxLine(String title, BigDecimal rate, BigDecimal price) {
        this.title = title;
        this.rate = rate;
        this.price = price;
    }
    public String getTitle() {
        return this.title;
    }
    public BigDecimal getRate() {
        return this.rate;
    }
    public BigDecimal getPrice() {
        return this.price;
    }
    @Override
    public String toString() {
        return "TaxLine = title:\"" + this.title + "\", rate:" + this.rate + ", price:" + this.price;
    }
}

And the grouping helper class defined like this:

public final class TaxGroup {
    private String title;
    private BigDecimal rate;
    public static TaxLine asLine(Entry<TaxGroup, BigDecimal> e) {
        return new TaxLine(e.getKey().getTitle(), e.getKey().getRate(), e.getValue());
    }
    public TaxGroup(TaxLine taxLine) {
        this.title = taxLine.getTitle();
        this.rate = taxLine.getRate();
    }
    public String getTitle() {
        return this.title;
    }
    public BigDecimal getRate() {
        return this.rate;
    }
    @Override
    public int hashCode() {
        return this.title.hashCode() * 31 + this.rate.hashCode();
    }
    @Override
    public boolean equals(Object obj) {
        if (obj == null || getClass() != obj.getClass())
            return false;
        TaxGroup that = (TaxGroup) obj;
        return (this.title.equals(that.title) && this.rate.equals(that.rate));
    }
}

Your code to combined the line items is this, split over many lines to help see its various parts:

List<TaxLine> lines = Arrays.asList(
        new TaxLine("New York Tax", new BigDecimal("0.20"), new BigDecimal("20.00")),
        new TaxLine("New York Tax", new BigDecimal("0.20"), new BigDecimal("20.00")),
        new TaxLine("County Tax"  , new BigDecimal("0.10"), new BigDecimal("10.00"))
);
List<TaxLine> combined =
        lines
        .stream()
        .collect(Collectors.groupingBy(TaxGroup::new,
                                       Collectors.reducing(BigDecimal.ZERO,
                                                           TaxLine::getPrice,
                                                           BigDecimal::add)))
        .entrySet()
        .stream()
        .map(TaxGroup::asLine)
        .collect(Collectors.toList());

You can then print input/output:

System.out.println("Input:");
lines.stream().forEach(System.out::println);
System.out.println("Combined:");
combined.stream().forEach(System.out::println);

To produce this:

Input:
TaxLine = title:"New York Tax", rate:0.20, price:20.00
TaxLine = title:"New York Tax", rate:0.20, price:20.00
TaxLine = title:"County Tax", rate:0.10, price:10.00
Combined:
TaxLine = title:"New York Tax", rate:0.20, price:40.00
TaxLine = title:"County Tax", rate:0.10, price:10.00
Andreas
  • 154,647
  • 11
  • 152
  • 247
0

Unable to sum up the grouped field can be achieved by Defining a new class called TitleRate inside Taxline class as below.

    class Taxline{
        public static class TitleRate {
            public TitleRate(String title, int taxline) {
                ...
            }

        }

        public TitleRate getTitleRate() {
            return new TitleRate(title, taxline);
        }
    }

To sum the price by grouping title and taxrate, below can be used.

Map<TitleRate, List<Taxline>> groupedData = people.collect(Collectors.groupingBy(Taxline::getTitleRate));

  List<Taxline> groupedTaxLines = new ArrayList<Taxline>();
  BigDecimal groupedRate = BigDecimal.ZERO;
  for (Map<TitleRate, List<Taxline>> entry : groupedData.entrySet())
  {
    for(Taxline taxline : entry.getValue()){
        groupedRate = groupedRate.add(taxline.getPrice());
    }
    groupedTaxLines.add(new Taxline(entry.getKey().getTitle, entry.getKey().getRate(), groupedRate));
        groupedRate = BigDecimal.ZERO;
   }
Srikanth Balaji
  • 2,638
  • 4
  • 18
  • 31