Search code examples
javajava-streamreducedivide

java8 reduce with multiple operation (sum and average) in one shot


I've a list of item "Fatturato" and i have compute the sum a field and the average of another field. I'm wondering if i can do it with an unique line of code using "the reduce" method, currently i've done it with multiple lines. I'd like use the divide method inside the reduce but i don't know how to define (do it only on the last iteration, when sum are over)

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class Main {

    public static void main (String args[]) {

        List<Fatturato> listFatturato = new ArrayList<Fatturato>();
        Fatturato ftt1 = new Fatturato(new BigDecimal(5000), new BigDecimal(20));
        Fatturato ftt2 = new Fatturato(new BigDecimal(1000), new BigDecimal(34));
        Fatturato ftt3 = new Fatturato(new BigDecimal(4000), new BigDecimal(67));
        listFatturato.add(ftt1);
        listFatturato.add(ftt2);
        listFatturato.add(ftt3);

        Optional<Fatturato> total = listFatturato.stream().reduce((f1, f2) -> new Fatturato()
                    .impFatturato(f1.getImpFatturato().add(f2.getImpFatturato()))
                    .percNostroPortafoglio(f1.getPercNostroPortafoglio().add(f2.getPercNostroPortafoglio()))

        );
        if (total.isPresent()) {
            MathContext m = new MathContext(3, RoundingMode.HALF_UP);
            Fatturato finalFatturato = total.get().percNostroPortafoglio(total.get().getPercNostroPortafoglio().divide(new BigDecimal(3), m));
            System.out.println(finalFatturato);
        }
    }
}

import java.math.BigDecimal;

public class Fatturato {

    private BigDecimal impFatturato;
    private BigDecimal percNostroPortafoglio;

    public Fatturato() {
        super();
    }

    public Fatturato(BigDecimal impFatturato, BigDecimal percNostroPortafoglio) {
        super();
        this.impFatturato = impFatturato;
        this.percNostroPortafoglio = percNostroPortafoglio;
    }

    public Fatturato impFatturato(BigDecimal impFatturato) {
        this.impFatturato = impFatturato;
        return this;
    }

    public Fatturato percNostroPortafoglio(BigDecimal percNostroPortafoglio) {
        this.percNostroPortafoglio = percNostroPortafoglio;
        return this;
    }

    public BigDecimal getImpFatturato() {
        return impFatturato;
    }


    public BigDecimal getPercNostroPortafoglio() {
        return percNostroPortafoglio;
    }

    @Override
    public String toString() {
        return "Fatturato [impFatturato=" + impFatturato + ", percNostroPortafoglio=" + percNostroPortafoglio + "]";
    }
}

Thanks


Solution

  • You are overthinking the issue. You cannot simply use one magical line to both sum and average the values while adding them to a new object. You approach through reduce is correct, yet it asks for a slight improvement using an advantage of the Optional<Fatturato> return type of the reduce method. Also don't forget that you are dealing with verbose classes Big* so the entire code gets bloated as long as Java doesn't support operator overloading, which I'd find useful for this use case.

    MathContext m = new MathContext(3, RoundingMode.HALF_UP);
    
    listFatturato.stream()
        // reducing >>>  Fatturato(sum, sum)
        .reduce((f1, f2) -> new Fatturato()
            .impFatturato(f1.getImpFatturato().add(f2.getImpFatturato()))
            .percNostroPortafoglio(f1.getPercNostroPortafoglio().add(f2.getPercNostroPortafoglio())))
        // calculating average using listFatturato.size() >>> Fatturato(sum, avg)
        .map(f -> new Fatturato(f.getImpFatturato(), f.getPercNostroPortafoglio()
              .divide(new BigDecimal(listFatturato.size()), m)))
        // printing out
        .ifPresent(System.out::println);