I have a collection of invoices :
class Invoice {
int month;
BigDecimal amount
}
I\'d like to merge these invoices, so I get one invoice pe
I think if your application do not support lambda than this might be a suitable answer eg (Android minSdkVersion=16 do not support lambda)
public static List<Invoice> mergeAmount(List<Invoice> invoiceList) {
List<Invoice> newInvoiceList = new ArrayList<>();
for(Invoice inv: invoiceList) {
boolean isThere = false;
for (Invoice inv1: newInvoiceList) {
if (inv1.getAmount() == inv.getAmount()) {
inv1.setAmount(inv1.getAmoount()+inv.getAmount());
isThere = true;
break;
}
}
if (!isThere) {
newInvoiceList.add(inv);
}
}
return newInvoiceList;
}
You can do something like
Map<Integer, Invoice> invoiceMap = invoices.stream()
.collect(Collectors.groupingBy( // group invoices by month
invoice -> invoice.month
))
.entrySet().stream() // once you have them grouped stream then again so...
.collect(Collectors.toMap(
entry -> entry.getKey(), // we can mantain the key (month)
entry -> entry.getValue().stream() // and streaming all month's invoices
.reduce((invoice, invoice2) -> // add all the ammounts
new Invoice(invoice.month, invoice.amount.add(invoice2.amount)))
.orElse(new Invoice(entry.getKey(), new BigDecimal(0))) // In case we don't have any invoice (unlikeable)
));
If you could add the following copy constructor and merge method to your Invoice
class:
public Invoice(Invoice another) {
this.month = another.month;
this.amount = another.amount;
}
public Invoice merge(Invoice another) {
amount = amount.add(another.amount); // BigDecimal is immutable
return this;
}
You could reduce as you want, as follows:
Collection<Invoice> result = list.stream()
.collect(Collectors.toMap(
Invoice::getMonth, // use month as key
Invoice::new, // use copy constructor => don't mutate original invoices
Invoice::merge)) // merge invoices with same month
.values();
I'm using Collectors.toMap to do the job, which has three arguments: a function that maps elements of the stream to keys, a function that maps elements of the stream to values and a merge function that is used to combine values when there are collisions on the keys.
If you are OK returning a Collection
it would look like this:
Collection<Invoice> invoices = list.collect(Collectors.toMap(Invoice::getMonth, Function.identity(), (left, right) -> {
left.setAmount(left.getAmount().add(right.getAmount()));
return left;
})).values();
If you really need a List
:
list.stream().collect(Collectors.collectingAndThen(Collectors.toMap(Invoice::getMonth, Function.identity(), (left, right) -> {
left.setAmount(left.getAmount().add(right.getAmount()));
return left;
}), m -> new ArrayList<>(m.values())));
Both obviously assume that Invoice
is mutable...
Collection<Invoice> result = invoices.stream().collect(groupingBy(i -> i.month,
collectingAndThen(
reducing((Invoice i1, Invoice i2) -> new Invoice(i1.month, i1.amount + i2.amount)),
Optional::get))).values();
Here is the solution by My library: AbacusUtil
Stream.of(invoices)
.groupBy2(Invoice::getMonth, Invoice::getAmount, BigDecimal::add)
.map(e -> new Invoice(e.getKey(), e.getValue())) // Probably we should not modify original invoices. create new instances.
.toList();