My list consists of variable Type(String), Amount(Double) and Quantity(Integer) and it look like this:
Type: Type A, Amount : 55.0, Quantity : 0
Type: Type A, Amount : 55.0, Quantity : 5
Type: Type A, Amount : 44.35, Quantity : 6
Type: Type A, Amount : 55.0, Quantity : 0
Type: Type B, Amount : 7.0, Quantity : 1
Type: Type B, Amount : 7.0, Quantity : 1
Type: Type C, Amount : 1613.57, Quantity : 0
Type: Type C, Amount : 1613.57, Quantity : 1
So i am trying to loop my array to find duplicate, and add the Amount if its duplicate. The outcome would be like this:
Type: Type A, Amount : 209.35.0, Quantity : 11
Type: Type B, Amount : 14.0, Quantity : 2
Type: Type C, Amount : 3227.14, Quantity : 1
What i have tried is creating another List, add the List to new List, then compare them, but didnt work
List<Type> newList = new ArrayList();
for(int k = 0; k < typeList.size(); k ) {
Type type= new Type();
Double totalAmount = Double.parseDouble("0");
type.setTypeName(typeList.get(k).getTypeName());
type.setAmount(chargeTypeList.get(k).getAmount());
newList.add(k, type);
if(typeList.get(k).getChargeTypeName().equalsIgnoreCase(newList.get(k).getiTypeName())) {
totalAmount = typeList.get(k).getAmount();
}
}
I do not wish to hard code the value to check for duplicate Type
CodePudding user response:
You should probably be putting these values into a Map, which guarantees there is only one element for each key. Using a map is very common for representing amounts of some thing where we store the thing as the key and keep track of how many of those things we have in the value.
You can use compute
to then add elements to the list.
What you currently have:
record Data(String type, Double amount, Integer quantity) {}
What may represent your data better:
record Datav2(Double amount, Integer quantity) {}
Storing Datav2 in a map and adding an element.
var map = new HashMap<>(Map.of("A", new Datav2( 2.0, 3)));
// add element to map equivalent to Data("A", 3.0, 3)
map.compute("A", (k, v) -> {
if (v == null) {
v = new Datav2(0.0, 0);
}
return new Datav2(v.amount = 3.0, v.quantity 3);
});
If you need to start with a list for whatever reason you can use the Stream
API to turn the list into a map. Specifically toMap.
var list = List.of(new Data("A", 2.0, 3),
new Data("A", 3.0, 3),
new Data("C", 2.0, 1),
new Data("B", 10.0, 3),
new Data("B", 2.0, 5)
);
var collected = list
.stream()
.collect(Collectors.toMap(
// what will the key be
Data::type,
// what will the value be
data -> new Datav2(data.amount, data.quantity),
// how do we combine two values if they have the same key
(d1, d2) -> new Datav2(d1.amount d2.amount, d1.quantity d2.quantity)
));
System.out.println(collected);
{A=Datav2[amount=5.0, quantity=6], B=Datav2[amount=12.0, quantity=8], C=Datav2[amount=2.0, quantity=1]}
CodePudding user response:
Another approach would be to sort the list by type, then iterate it and add each item to an sum item. When the type changes, add your sum item to a result list and keep going.
CodePudding user response:
Another way for achieving is by use of collect & hashmap's merge operation:
List<TypeClass> ls = List.of(new TypeClass("A", 12.3, 2), new TypeClass("A", 3.4, 4),
new TypeClass("B", 12.4, 6), new TypeClass("B", 12.8, 8));
System.out.println(
ls.stream().collect(HashMap<String, TypeClass>::new, (x, y) -> x.merge(y.getTypeName(), y, (o, p) -> {
return new TypeClass(y.getTypeName(), o.getAmount() p.getAmount(),
o.getQuantity() p.getQuantity());
}), (a, b) -> a.putAll(b)));
this will print following output:
{A=TypeClass [typeName=A, amount=15.700000000000001, quantity=6], B=TypeClass [typeName=B, amount=25.200000000000003, quantity=14]}
Here, we are accumulating hashmap which is merged based on key i.e. your string value. Merged function is simple addition of amount & quantity of your Type Class.
CodePudding user response:
You can use built-in collector groupingBy()
to group the objects having the same type
in conjunction with a custom collector created via Collector.of()
as downstream of grouping.
Assuming that your custom object looks like this (for the purpose of conciseness, I've used a Java 16 record):
public record MyType(String type, double amount, int quantity) {}
Note:
Don't use wrapper-types without any good reason, uses primitives instead. That would allow avoiding unnecessary boxing/unboxing and eliminates the possibilities of getting a
NullPointerException
while performing arithmetical operations or comparing numeric values.If the number values that
type
attribute might have is limited, then it would be better to use anenum
instead ofString
because it's more reliable (it would guard you from making a typo) and offers some extra possibilities since enums have an extensive language support.
That's how the accumulation logic can be implemented:
List<MyType> typeList = new ArrayList();
List<MyType> newList = typeList.stream()
.collect(Collectors.groupingBy(
MyType::type,
Collector.of(
MyAccumulator::new,
MyAccumulator::accept,
MyAccumulator::merge
)
))
.entrySet().stream()
.map(entry -> new MyType(entry.getKey(),entry.getValue().getAmount(), entry.getValue().getQuantity()))
.toList();
And that's how the custom accumulation type internally used by the collector might look like:
public static class MyAccumulator implements Consumer<MyType> {
private double amount;
private int quantity;
@Override
public void accept(MyType myType) {
add(myType.amount(), myType.quantity());
}
public MyAccumulator merge(MyAccumulator other) {
add(other.amount, other.quantity);
return this;
}
private void add(double amount, int quantity) {
this.amount = amount;
this.quantity = quantity;
}
// getters
}