I have class Data10
which have 4 fields.
And I have a list of Data10
objects.
I want to do an arithmetic operation with values of the elements with the same ID. As a final result I need to generate new Data10
object with same ID, same year
, new name
("D"
), and value
(based on arithmetic operation).
public class Data10 {
int id;
int year;
String name;
BigDecimal value;
}
List<Data10> list = new ArrayList();
list.add(new Data10(1, 2020, "A", new BigDecimal(5.5)));
list.add(new Data10(1, 2020, "B", new BigDecimal(2.5)));
list.add(new Data10(1, 2020, "C", new BigDecimal(8.5)));
list.add(new Data10(2, 2020, "A", new BigDecimal(1.5)));
list.add(new Data10(2, 2020, "B", new BigDecimal(6.5)));
list.add(new Data10(2, 2020, "C", new BigDecimal(2.5)));
list.add(new Data10(3, 2020, "A", new BigDecimal(6.5)));
list.add(new Data10(3, 2020, "B", new BigDecimal(1.5)));
list.add(new Data10(3, 2020, "C", new BigDecimal(9.5)));
list.add(new Data10(4, 2020, "A", new BigDecimal(3.5)));
list.add(new Data10(4, 2020, "B", new BigDecimal(7.5)));
list.add(new Data10(4, 2020, "C", new BigDecimal(5.5)));
I want to apply the below formula for each group of ID:
D = A - (B * C) [Airthmetic operation for group of objects with the same ID]
I applied filter for object names ("A|B|C"
) so that only element with these names can appear in the list. And there will be only one unique object for each name and Id.
I've tried grouping the list based on ID but don't know how to apply the formula in each group.
list.stream().filter(data10 -> data10.getName().matches("A|B|C"))
.collect(Collectors.groupingBy(Data10::getId));
Expected:
Data10(1, 2020, "D", -15.75); [ Formula (D= 5.5 - (2.5 * 8.5)) ]
Data10(2, 2020, "D", -14.75); [ Formula (D= 1.5 - (6.5 * 2.5)) ]
Data10(3, 2020, "D", -7.75); [ Formula (D= 6.5 - (1.5 * 9.5)) ]
Data10(4, 2020, "D", -37.75); [ Formula (D= 3.5 - (7.5 * 5.5)) ]
You can group these objects first by id
and then by name
with Collectors.groupingBy
which will produce a nested map. And then reduce this intermediate map into list by producing a new data-object for each value in the map.
public static void main(String[] args) {
List<Data10> data10List = // initializing the list
List<Data10> result =
data10List.stream()
.collect(Collectors.groupingBy(Data10::getId, // by id
Collectors.groupingBy(Data10::getName))) // by name
.values().stream()
.map(map -> processValues(map))
.collect(Collectors.toList());
result.forEach(System.out::println); // printing the result
}
For the method below, I've made an assumption that all combinations id
and name
are unique. Therefore, every list in the map will contain only one element (Note: hard-coded names as well as hard-coded math logic, normally has to be avoided).
public static Data10 processValues(Map<String, List<Data10>> map) {
BigDecimal combinedValue =
map.get("A").get(0).getValue() // "A"
.subtract(map.get("B").get(0).getValue() // "B"
.multiply(map.get("C").get(0).getValue())); // "C"
return new Data10(map.get("A").get(0).getId(),
map.get("A").get(0).getYear(),
"D",
combinedValue);
}
To avoid hard-coding the logic for the math operation, you can define a custom functional interface (there's no build-in function in the JDK that expects three argument). And make the method processValues()
more generalized by adding this interface and expected names as parameters to the declaration. So that it'll be possible to apply different math logic without need to change the method.
An interface and implementation might look like that:
@FunctionalInterface
interface TripleFunction<A, B, C, R> {
R calculate(A a, B b, C c);
}
TripleFunction<BigDecimal, BigDecimal, BigDecimal, BigDecimal> mathOperation =
(a, b, c) -> a.subtract(b.multiply(c));
These are the changes required to utilize this function (in the stream pipeline and inside the processValues
):
.map(map -> processValues(map, mathOperation, "A", "B", "C"))
public static Data10 processValues(Map<String, List<Data10>> map,
TripleFunction<BigDecimal, BigDecimal, BigDecimal, BigDecimal> mathOperation,
String a, String b, String c) {
BigDecimal combinedValue = mathOperation
.calculate(map.get(a).get(0).getValue(),
map.get(b).get(0).getValue(),
map.get(c).get(0).getValue());
return new Data10(map.get("A").get(0).getId(),
map.get("A").get(0).getYear(),
"D",
combinedValue);
}
Output (data listed in the question, was used as an input)
Data10{id=1, year=2020, name='D', value=-15.75}
Data10{id=2, year=2020, name='D', value=-14.75}
Data10{id=3, year=2020, name='D', value=-7.75}
Data10{id=4, year=2020, name='D', value=-37.75}