iamthereplicant
iamthereplicant

Reputation: 13

Reparse and map elements from list

I'm relatively new to Java 8 and trying to wrap my head around streams. I have a query from a database that returns the following:

String companyName | 
String clientName | 
BigDecimal amount | 
String transactionType (either credit or debit) | 
long numberOfTransactions

I store each row in this object, using the value of the transactionType field to determine which of creditAmount or debitAmount amount gets filled

public RowForCsv{
    private String companyName;
    private String customerName;
    private BigDecimal creditAmount;
    private BigDecimal debitAmount;
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((companyName == null) ? 0 : companyName.hashCode());
        result = prime * result + ((customerName == null) ? 0 : customerName.hashCode());
        return result;
    }
}

I need to use this data to make separate CSV files for the transactions associated with each company name, meaning ultimately I'd like a

`Map<String companyName, List<RowForCsv> associatedTransactions>`

Since clients can issue both credits and debits I'd also like to merge separate RowForCsv objects with the same customer and company names into one RowForCsv object.

Here's what I've tried:

//Get Map<name, list of transactions>
Map<String, List<ReconciliationRecordForCsv>> mapByCompanyName = records.stream().collect(Collectors.groupingBy(ReconciliationRecordForCsv::getCompanyName));
// Merge duplicates
mapByCompanyName.replaceAll((k, v) -> {
    v.stream().collect(Collectors.collectingAndThen(
        Collectors.groupingBy(RowForCsv::hashCode), 
            Collectors.collectingAndThen(Collectors.reducing((a, b) -> mergeCreditAndDebitRecords(a, b)), Optional::get)), 
                m -> new ArrayList<>(m.values()));
    });

This is my merging function, which I think I've misunderstood the concept of...

private RowForCsv mergeCreditAndDebitRecords(RowForCsv first, RowForCsv second) {
    RowForCsv.Builder merged = new RowForCsv.Builder();
    return merged.companyName(first.getCompanyName())
            .customerName(first.getCustomerName())
            .creditAmount(getLarger(first.getCreditAmount(), second.getCreditAmount()))
            .debitAmount(getLarger(first.getDebitAmount(), second.getDebitAmount()))
            .build();
}

At this point I'm getting errors relating to the replace all (k,v) having no types, the collectingAndThens are all throwing errors related to their chaining, and the merge function is not valid for types (Object, Object).

I have a feeling I'm approaching this in the wrong way but I don't see what I should be doing differently, and any guidance would be greatly appreciated.

Upvotes: 1

Views: 86

Answers (2)

user_3380739
user_3380739

Reputation: 1254

If I understand your question correctly: you want to group by the records by company name, and merge the transactions by maximum creditAmount/debitAmount with same customer name. Here is my solution by abacus-common

// merge two records with same customer name by using the maximum creditAmount/debitAmount
BinaryOperator<RowForCsv> mergeFunction = (a, b) -> {
    RowForCsv c = new RowForCsv();
    c.companyName = a.companyName;
    c.customerName = a.customerName;
    c.creditAmount = N.max(a.creditAmount, b.creditAmount);
    c.debitAmount = N.max(a.creditAmount, b.creditAmount);
    return c;
};

Map<String, Collection<RowForCsv>> mergedAmountByCompanyName = 
  Stream.of(records).groupBy(e -> e.getCompanyName())
  .toMap(e -> e.getKey(),
         e -> Stream.of(e.getValue())
             .toMap(e2 -> e2.getCustomerName(), e2 -> e2, mergeFunction).values());

Upvotes: 0

xiumeteo
xiumeteo

Reputation: 961

I think this could be a solution:

final Map<String, List<Optional<RowForCsv>>> collect1 =
            rows.stream().collect(Collectors.groupingBy(RowForCsv::getCompanyName, Collectors.collectingAndThen(Collectors.toList(), byBank -> {
                return byBank.stream() //
                    .collect(Collectors.groupingBy(RowForCsv::getCustomerName)).values().stream()
                    .map(byUser -> byUser.stream().reduce((r1, r2) -> r2)).collect(Collectors.toList()); //
            })));

Another approach could be to "reduce" the initial rows so that you have unique rows per User, and then groupBy companies.

If I have more time I will get back to this. Interesting issue tho. Maybe you can try improving your queries from db. Grouping by from the db side it is always faster.

Upvotes: 1

Related Questions