2

I am trying to use Java-8 lambdas to solve the following problem:

Given a List<Transaction>, for each Category.minorCategory I require the sum of Transaction.amount per Category.minorCategory and a Map of Transaction.accountNumber with the sum of Transaction.amount per Transaction.accountNumber. I have this working, as per the code below. I now have a requirement to group by Category.majorCategory, essentially returning a Map<String, Map<String, MinorCategorySummary>> keyed on Category.majorCategory.

I have everything working up until the stage of grouping by Category.majorCategory but struggle to see the solution; the paradigm shift of programming with lambdas is proving a steep learning curve.

TransactionBreakdown is where the action happens and where I'd like to return a Map<String, Map<String, MinorCategorySummary>>.

public class Transaction {

    private final String accountNumber;
    private final BigDecimal amount;
    private final Category category;

}

public class Category {

    private final String majorCategory;
    private final String minorCategory;

}

public class MinorCategorySummary {

    private final BigDecimal sumOfAmountPerMinorCategory;
    private final Map<String, BigDecimal> accountNumberSumOfAmountMap;
    private final Category category;

}

public class TransactionBreakdown {

    Function<Entry<String, List<Transaction>>, MinorCategorySummary> transactionSummariser = new TransactionSummariser();

    public Map<Object, MinorCategorySummary> getTransactionSummaries(List<Transaction> transactions) {
        return transactions
                .stream()
                .collect(groupingBy(t -> t.getCategory().getMinorCategory()))
                .entrySet()
                .stream()
                .collect(
                        toMap(Entry::getKey,
                                transactionSummariser));
    }

}

public class TransactionSummariser implements Function<Entry<String, List<Transaction>>, MinorCategorySummary> {

    @Override
    public MinorCategorySummary apply(
            Entry<String, List<Transaction>> entry) {
         return new MinorCategorySummary(
                    entry.getValue()
                            .stream()
                            .map(Transaction::getAmount)
                            .collect(reducing(BigDecimal.ZERO, BigDecimal::add)),
                    entry.getValue()
                            .stream()
                            .collect(
                                    groupingBy(Transaction::getAccountNumber,
                                            mapping(Transaction::getAmount,
                                                    reducing(BigDecimal.ZERO, BigDecimal::add)))), 
                                                    entry.getValue().get(0).getCategory());
    }

}

1 Answer 1

5

Your class design seems odd to me. Why put category into the summary class only to then have the category as a map key? It would make more sense to have a summary class without category in it:

public class TransactionSummary {

    private final BigDecimal amount;
    private final Map<String, BigDecimal> acctToTotal;

    TransactionSummary(Map<String, BigDecimal> acctToTotal) {
        this.acctToTotal = Collections.unmodifiableMap(acctToTotal);
        this.amount = acctToTotal.values().stream()
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public static Collector<Transaction, ?, TransactionSummary> collector() {
        // this can be a static constant
        return collectingAndThen(
            toMap(Transaction::getAccountNumber,Transaction::getAmount,BigDecimal::add),
            TransactionSummary::new
        );
    }

    // getters
}

Now your two problems are solved clearly and with no redundancy:

Map<String, TransactionSummary> minorSummary = transactions.stream()
        .collect(groupingBy(
                t -> t.getCategory().getMinorCategory(),
                TransactionSummary.collector()
        ));

Map<String, Map<String, TransactionSummary>> majorMinorSummary = transactions.stream()
        .collect(groupingBy(
                t -> t.getCategory().getMajorCategory(),
                groupingBy(
                        t -> t.getCategory().getMinorCategory(),
                        TransactionSummary.collector()
                )
        ));
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your solution, it looks so simple when you see it but I was struggling to get there. Regarding the category, you're right, it shouldn't be in the summary - it made it in there after several hours of trying to get my head around lambdas - I was getting desperate :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.