I have class DTO which have 3 fields.
And I have a list of DTO objects.
I want to do remove all the duplicate object based on ID field in the list and get the sum of the value which is string type. First values convert to Bigdecimal and then add(Sum) the duplicate object values.
Note:: Value is string format in the class DTO
@AllArgsConstructor
static class DTO {
int id;
int year;
String value;
}
List<DTO> list = new ArrayList();
list.add(new DTO(1, 2020, "5.5"));
list.add(new DTO(1, 2020, "-8.0"));
list.add(new DTO(2, 2020, "1.5"));
list.add(new DTO(3, 2020, "4.5"));
list.add(new DTO(3, 2020, "1.5"));
list.add(new DTO(3, 2020, "-9.5"));
list.add(new DTO(4, 2020, "-3.5"));
list.add(new DTO(4, 2020, "7.5"));
list.add(new DTO(4, 2020, "5.5"));
list.add(new DTO(4, 2020, "-7.5"));
I've tried a variations of streams, collectors, groupby etc., and I'm lost.
This is what I have so far:
list.stream().collect(Collectors.groupingBy(e -> e.getId()), Collectors.reducing(BigDecimal.ZERO, DTO::getValue, BigDecimal.add()));
Expected
DTO(1, 2020, "-2.5"); (value Sum of group 1)
DTO(2, 2020, "-1.5"); (value Sum of group 2)
DTO(3, 2020, "-3.5"); (value Sum of group 3)
DTO(4, 2020, "2"); (value Sum of group 4)
CodePudding user response:
You can create a method that adds two DTO
:
private static DTO add(DTO dto1, DTO dto2) {
BigDecimal bd1 = new BigDecimal(dto1.getValue());
BigDecimal bd2 = new BigDecimal(dto2.getValue());
return new DTO(dto1.getId(), dto1.getYear(), bd1.add(bd2).toString());
}
Then you can stream
grouping by id
and reduce using the previous method:
private static List<DTO> add(List<DTO> list) {
Map<Integer, Optional<DTO>> map = list.stream()
.collect(Collectors.groupingBy(DTO::getId,
Collectors.reducing((d1, d2) -> add(d1, d2))));
return map.values().stream()
.filter(Optional::isPresent)
.map(Optional::get).toList();
}
Test:
List<DTO> list = List.of(
new DTO(1, 2020, "5.5"),
new DTO(1, 2020, "-8.0"),
new DTO(2, 2020, "1.5"),
new DTO(3, 2020, "4.5"),
new DTO(3, 2020, "1.5"),
new DTO(3, 2020, "-9.5"),
new DTO(4, 2020, "-3.5"),
new DTO(4, 2020, "7.5"),
new DTO(4, 2020, "5.5"),
new DTO(4, 2020, "-7.5"));
List<DTO> listAdded = add(list);
listAdded.forEach(System.out::println);
Output:
DTO[1, 2020, -2.5]
DTO[2, 2020, 1.5]
DTO[3, 2020, -3.5]
DTO[4, 2020, 2.0]
CodePudding user response:
If I were doing this I would not use streams but the merge method which takes the current value and applies it to the existing one, which in this case is the instance that holds the sums.
List<DTO> list = List.of(new DTO(1, 2020, "5.5"),
new DTO(1, 2020, "-8.0"), new DTO(2, 2020, "1.5"),
new DTO(3, 2020, "4.5"), new DTO(3, 2020, "1.5"),
new DTO(3, 2020, "-9.5"), new DTO(4, 2020, "-3.5"),
new DTO(4, 2020, "7.5"), new DTO(4, 2020, "5.5"),
new DTO(4, 2020, "-7.5"));
Map<Integer, DTO> map = new HashMap<>();
for (DTO dto : list) {
map.merge(dto.getId(), dto, (sum, dt)-> new DTO(sum.getId(),
sum.getYear(),
new BigDecimal(sum.getValue()).add(
new BigDecimal((dt.getValue()))).toString()));
}
map.values().forEach(System.out::println);
prints
DTO[1, 2020, -2.5]
DTO[2, 2020, 1.5]
DTO[3, 2020, -3.5]
DTO[4, 2020, 2.0]
If you want to place the results in a list you can do.
List<DTO> newList = new ArrayList<>(map.values());
Besides getters and setters, I am using the following toString for output.
@Override
public String toString() {
return "%s[%d, %d, %s]".formatted(this.getClass().getSimpleName(),id, year, value);
}
But if you really want to use a stream, then you can do like so.
- Stream the objects
- Collect using toMap
- and use toMap's merge method to sum the values.
Map<Integer, DTO> result = list.stream()
.collect(Collectors.toMap(DTO::getId, dto->dto,
(sum, dto)->new DTO(
sum.getId(), sum.getYear(),
new BigDecimal(sum.getValue())
.add(new BigDecimal((dto.getValue())))
.toString())));