如何对列表中具有相同 ID 的每组元素应用算术运算

How to apply an Arithmetic operation on each Group of elements with the same ID in the list

我有 class Data10 有 4 个字段。

我有一个 Data10 个对象的列表。

我想对具有 相同 的元素的 进行 算术运算 ID。作为最终结果,我需要生成具有相同 ID、相同 year、新 name("D") 的新 Data10 对象,和value(基于算术运算)。

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)));

我想对每组 ID 应用下面的 公式:

D = A - (B * C) [Airthmetic operation for group of objects with the same ID]

我对对象名称 ("A|B|C") 应用了过滤器,以便只有具有这些名称的元素才能出现在列表中。并且每个名称和 Id 将只有一个唯一对象。

我试过根据 ID 对列表进行分组,但不知道如何在每个组中应用该公式。

list.stream().filter(data10 -> data10.getName().matches("A|B|C"))
            .collect(Collectors.groupingBy(Data10::getId));

预期:

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)) ]

按id对列表进行分组,然后循环遍历每个组并获取a、b和c值, 将新数据添加到具有 D 名称和公式值的列表中。

   list.stream()
                .collect(Collectors.groupingBy(Data10::getId))
                .entrySet().forEach(e -> {
                    var a = e.getValue().stream().filter(d -> d.getName().equals("A")).findFirst().get().getValue();

                    var b = e.getValue().stream().filter(d -> d.getName().equals("B")).findFirst().get().getValue();

                    var c = e.getValue().stream().filter(d -> d.getName().equals("C")).findFirst().get().getValue();

                    var formula = new BigDecimal(a.subtract(b.multiply(c)).doubleValue());
                    list.add(new Data10(e.getKey(), 2020, "D", formula));
                });


您可以先通过 id 对这些对象进行分组,然后通过 nameCollectors.groupingBy 进行分组,这将生成嵌套地图。然后通过为地图中的每个值生成一个新的 data-object 将这个中间地图缩减为列表。

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
}

对于下面的方法,我假设所有组合 idname是独一无二的。因此,地图中的每个列表将只包含一个元素(注意:hard-coded names 以及 hard-coded math logic, 通常要避免).

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);
}

为了避免hard-coding数学运算的逻辑,你可以定义一个自定义的函数式接口([=57中没有build-in函数=] 需要三个参数)。并通过将此接口和预期名称作为参数添加到声明中,使方法 processValues() 更加通用。这样就可以应用不同的数学逻辑而无需更改方法。

接口和实现可能如下所示:

@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));

这些是使用此函数所需的更改(在流管道和 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);
}

输出问题中列出的数据,用作输入

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}