-1

assume stream of txn, where each txn has following fields

{ date, amt, name, merchant }
given txn stream
group by yyyy-mm // fold 1
within group(yyyy-mm); Map to new object Expense(income, spent)
if amount > 0 then income+=amt // conditional fold 2.a
if amount < 0 then spent+=amt // conditional fold 2.b

am wondering what are approach(es) to achieve above in java. Tried below, it falls way short

    txns
                .stream()
                .collect(
                    Collectors.groupingBy(
                        Txn::getYearMonth, 
                        Collectors.mapping(
                            getExpense,
                            Collectors.toList()))); 

getexpense is function map I wrote to transform Txn to Expense object.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
bajju
  • 1
  • 2

1 Answers1

0

I think you are stuck on the aggregate step. The code you posted will result in this:

Map<YearMonth, List<Expense>>

While I'm guessing you want:

Map<YearMonth, Expense>  // transactions aggregated into a single Expense

In order to do this you need to create a collector that can aggregate Expense objects.

It would look like this:

Collector.of(
  Expense::new,
  (Expense e, Transaction t) -> e.add(t),
  (e1, e2) -> new Expense(e1, e2)
);

Your expense class will need:

  1. a public empty constructor
  2. a constructor that takes 2 expenses and creates a new aggregated expense
  3. an add method that adds a Transaction to an Expense

Something like this:

public class Expense {
  private double income;
  private double spent;

  public Expense() {
  }

  public Expense(Expense e1, Expense e2) {
    this.income = e1.income + e2.income;
    this.spent = e1.spent + e2.spent;
  }

  public void add(Transaction t) {
    this.income += t.getAmount() > 0 ? t.getAmount() : 0;
    this.spent += t.getAmount() < 0 ? t.getAmount() : 0;
  }
}

You can then use this Collector in the groupingBy:

groupingBy(Txn::getYearMonth, myCollector)

And the result will be aggregated expenses in a single Expense object.

Update

So like this:

Collector<Transaction, Expense, Expense> c = Collector.of(
  Expense::new,
  (Expense e, Transaction t) -> e.add(t),
  (e1, e2) -> new Expense(e1, e2)
);

Map<YearMonth, Expense> collect = txns.stream()
    .collect(Collectors.groupingBy(Transaction::getYearMonth, c));
john16384
  • 7,800
  • 2
  • 30
  • 44
  • culd u help me define myCollector – bajju Mar 17 '17 at 14:41
  • I suppose, the amount of the transaction should be added to `income` or `spent` rather than overwriting it… – Holger Mar 17 '17 at 16:19
  • Yeah, seems I missed that, updating it – john16384 Mar 17 '17 at 16:26
  • @john16384 above code does not compile, unless I change to belowCollector.of( Expense::new, (Expense e, Transaction t) -> e.add(t), (e1, e2) -> new Expense(e1, e2) ); – bajju Mar 17 '17 at 17:40
  • Error below on line The method mapping(Function super T,? extends U>, Collector super U,A,R>) in the type Collectors is not applicable for the arguments (Function, Collector) TxnsController.java /deepak/src/main/java/com/example – bajju Mar 17 '17 at 18:00