3
public class BusinessLog {

private Date logDate;
private double prize;

}

Given the object BusinessLog, I need to have sum for all the prizes in List<BusinessLog> list also need to return the lowest date from the list, is it possible with lambda,

I can do it with forEach for sure, but how to do it with lambda,

What I tried to do until now is ,

    BigDecimal balance = BigDecimal.ZERO;

    if (list != null) {
        list.forEach(businessLog -> {
            balance.add(BigDecimal.valueOf(businessLog.getPrize()));
            // how to get lowest date 

        });
    }
tyro
  • 765
  • 2
  • 13
  • 34

2 Answers2

1

To compute multiple custom aggregations, you should write a custom reducer.

For example, the following reducer calculates the stats in one BusinessLog object. The following supposes a constructor public BusinessLog(double prize, Date logDate) to be declared.

BusinessLog stats = 
        bl.stream()
        .reduce((log1, log2) -> new BusinessLog(
                log1.getPrize() + log2.getPrize(),
                log1.getLogDate().before(log2.getLogDate()) ?
                     log1.getLogDate() : log2.getLogDate()
            )).get();

Date lowestDate = stats.getLogDate();
double prizeSum = stats.getPrize();

Please note that Using BusinessLog as a temporary stats holder is essentially a hack. You'd need to design a separate class for that purpose.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • But then at the same time I need lowest date as well, so do I write the lambda expression again for date? – tyro May 24 '18 at 15:09
  • 2
    @tyro you could build a custom collector for that, but you can also stream twice – Eugene May 24 '18 at 15:15
  • @tyro Added an example that uses a reducer to calculate both aggregations in one stream traversal – ernest_k May 24 '18 at 15:23
  • 1
    @ErnestKiwele can't tell why the downvote here... take a +1 from me. btw, you can't easily create a dedicate class for that, how will you specify an identity for `Date`? – Eugene May 24 '18 at 15:46
  • @Eugene Thanks (the post was edited). I believe there's no "identity" concern for the reduce operation. All that's need is combining two objects into a new one. But you're right, a dedicated class isn't needed, just a data structure that's easy to "reduce". – ernest_k May 24 '18 at 15:55
  • @ErnestKiwele exactly my point, let's imagine that you are reducing to `SimpleEntry` as it holds two values, right? What would this constructor take as arguments? For `BigInteger` it's easy, but for `Date`? You need to specify an identity ... I don't know an "identity" for Date – Eugene May 24 '18 at 16:03
  • @Eugene I think I see what you're saying. Correct me if I misunderstood. There wouldn't be a generic "date identity", but one can conceive of a sort of identity for the reduce operation at hand. In this case of "min", a reducer can decide to use null as identity, assuming it's correctly implemented (I do see how brittle that code will be). But I agree that from there things stop making sense very quickly, making that class useless beyond the context of that reduction. – ernest_k May 24 '18 at 16:18
  • @ErnestKiwele exactly. The other answer makes use of null as identity, but its as ugly as it can get... – Eugene May 24 '18 at 16:22
  • 1
    @Ernest Kiwele Thanks for your reply, but like the idea of keeping them separate loop – tyro May 25 '18 at 08:49
1

A solution using streaming

To find the minimum date, you can do something like this:

Optional<Date> minDate = list.stream().map(v -> v.logDate).min(Date::compareTo);

And to calculate the sum:

double sum = list.stream().mapToDouble(v -> v.prize).sum();

I wouldn't worry about "optimizing" this and trying to do it in one loop unless this is provably a major bottleneck in your system (unlikely). Keeping the two ideas separate makes the code easier to understand and maintain.

Your use of BigDecimal

Your code for doing balance.add(...) to a BigDecimal won't actually work the way you've written it because the add method on BigDecimal returns a new instance rather than mutating the existing instance. BigDecimal instances are immutable. You can't assign a new value to balance because it's effectively final from the context of the lambda.

The idea of using BigDecimal is a good one though. You should avoid using double for anything where exact decimal places are important (e.g. money). If you change prize to a BigDecimal you can't use sum() but you can use reduce() to fulfil the same function.

BigDecimal sum = list.stream().map(v -> v.prize).reduce(BigDecimal.ZERO, BigDecimal::add);
ᴇʟᴇvᴀтᴇ
  • 12,285
  • 4
  • 43
  • 66