5 votes

Grouper par deux champs puis additionner BigDecimal

J'ai une liste de 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

La classe TaxLine est

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

Je voudrais les combiner en fonction de leur caractère unique title y rate puis ajoutez le price attendu :

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

Comment puis-je faire cela en Java 8 ?

Regroupement par plusieurs noms de champs en java 8 ne fait pas la somme d'un champ, il ne peut que regrouper par les deux champs.

9voto

shmosel Points 4840

Le principe est le même que dans la question liée, vous avez juste besoin d'un collecteur en aval différent pour faire la somme :

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());

0voto

Srikanth A Points 1909

L'impossibilité de résumer le champ groupé peut être obtenue en définissant une nouvelle classe appelée TitleRate dans la classe Taxline comme ci-dessous.

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

        }

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

Pour additionner le prix en regroupant le titre et le taux de taxe, on peut utiliser la méthode ci-dessous.

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;
   }

0voto

Andreas Points 3334

L'une des solutions consiste à créer un objet pour l'ensemble des champs que vous souhaitez regrouper. Cette classe peut être construite pour fournir des méthodes d'aide intéressantes.

Donc, avec votre classe originale complétée comme ceci :

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;
    }
}

Et la classe d'aide au regroupement est définie comme suit :

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));
    }
}

Votre code pour combiner les postes de ligne est le suivant, réparti sur plusieurs lignes pour faciliter la visualisation de ses différentes parties :

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());

Vous pouvez ensuite imprimer les entrées/sorties :

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

Pour produire ceci :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X