如何获得字段的不同计数并对必须按 Java 8 中的某些字段分组的列表中的字段求和?

How to get distinct count of a field and sum a field inside a list which has to be grouped by some fields in Java 8?

我需要找到唯一名称计数,以及列表中字段值的总和

下面是我的POJO:

@Getter
@Setter
public class Orders {
   private String dealId;
   private String fieldId;
   private String accountName;
   private List<Demands> demands;   //Demands contains multiple fields inside it, but I just need 
                                    //the 'amount' value mainly which is a BigDecimal.
}

List of Orders(ordersList) :

DealId  | FieldId  |  AccountName  |  Demand  |
1       |  11      |  ABC          |  100,100 |
1       |  11      |  ABC          |  200     |
1       |  11      |  PQR          |  300,100 |
1       |  12      |  ABC          |  100     |
2       |  21      |  ABC          |  200     |
2       |  21      |  PQR          |  300,500 |
2       |  21      |  XYZ          |  100     |
2       |  22      |  ABC          |  200,100 |
2       |  22      |  ABC          |  300     |

End Result :

DealId  |  FieldId  | AccountNameCount   |  DemandSum  |
1       |  11       |  2                 |   800       |
1       |  12       |  1                 |   100       | 
2       |  21       |  3                 |   1100      |
2       |  22       |  1                 |   600       | 

我需要以这种格式在某些 POJO 或集合中获取最终结果,其中

AccountNameCount 是交易的特定 FieldId 的唯一帐户名称的计数。 DemandSum 是 DealId 下特定 FieldId 的需求值的总和。

注意:Demand是Orders模型中的一个List,所以它可能有多个值,DealId下的特定FieldId的所有值都应该加上。

例如:DealId 1,FieldId 11,有2个不同的accountName,将所有需求值加到该fieldid下,dealId = 800

我正在尝试使用 Java 8 个流,已经能够按 dealId 对列表进行分组,然后是 fieldId,但我不确定如何计算 fieldId 级别的唯一帐户名称,并且需求值求和,不影响性能。

Map<String, Map<String, List<Orders>>> map = ordersList.stream()
    .collect(Collectors.groupingBy(Orders::getDealId, Collectors.groupingBy(Orders::getFieldId)));

Collectors.reducing 选项可用于添加到流中以添加需求值,但由于需求是订单中的列表,因此不适用于此处。

Collectors.reducing(BigDecimal.ZERO, Demands::getAmount, BigDecimal::add);

感谢任何帮助!

也许是这样的?

    return orderList.stream()
            .collect(Collectors.groupingBy(o -> Arrays.asList(o.dealId, o.fieldId)))
            .entrySet().stream().map(e -> {
                var stats = new OrderStats();
                stats.dealId = e.getKey().get(0);
                stats.fieldId = e.getKey().get(1);
                stats.accountNameCount = e.getValue().stream()
                        .map(o -> o.accountName)
                        .distinct()
                        .count();
                stats.demandSum = e.getValue().stream()
                        .flatMap(o -> o.demands.stream())
                        .map(d -> d.amount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                return stats;
            })
            .collect(Collectors.toList());

我使用 List 作为键以避免在分组 Map 中不必要的嵌套。这是可行的,因为 List 实现了基于内容的平等。

首先,让我们从按 dealIdfieldId

分组开始

之后我们需要的是 accountName 不同的计数和 demands 的总和,但这些在列表中可用,它在 Map 中 为了在地图上生成流,我们将使用 entrySet

这将为我们提供 Map.Entry>

要从 Map.Entry 访问键值,我们使用方法 getKey and getValue

// group by dealId and fieldId
Map<String, Map<String, List<Orders>>> map = ordersList.stream()
    .collect(Collectors.groupingBy(Orders::getDealId, Collectors.groupingBy(Orders::getFieldId)));

List<OrdersSummary> result = map
    .entrySet()
    .stream()
    .map(Test::getOrdersSummaries)
    .flatMap(List::stream)
    .collect(Collectors.toList());

System.out.println(result);

  private static List<OrdersSummary> getOrdersSummaries(Map.Entry<String, Map<String, List<Orders>>> entry) {
    return entry.getValue()
        .entrySet()
        .stream()
        .map(e2 -> toOrdersSummary(entry.getKey(), e2))
        .collect(Collectors.toList());
  }

  private static OrdersSummary toOrdersSummary(String dealId, Map.Entry<String, List<Orders>> entry){
    long accountNameCount= entry.getValue().stream().map(Orders::getAccountName).distinct().count();
    BigDecimal demandSum = entry.getValue().stream().map(Orders::getDemands)
        .flatMap(List::stream)
        .reduce(BigDecimal.ZERO, BigDecimal::add);
    return new OrdersSummary(dealId, entry.getKey(), accountNameCount, demandSum);
  }

完整代码Demo

尽管您可以通过 java 8 流 API 获得结果(可能有点不可读)。 但是在 java 12+ 之后提供了另一个收集器(teeing),可以帮助您以更具可读性的方式编写它。

public static Collector teeing​ (Collector downstream1, 
                            Collector downstream2, 
                            BiFunction merger); 

解决方案:您需要创建一些辅助对象,例如DetailResult

orders.stream()
      .collect(groupingBy(order -> Arrays.asList(order.getDealId(), order.getFieldId()),
        teeing(  
                 // unique account names for each group
                 mapping(Orders::getAccountName, Collectors.toSet()),
                 // sum all demands amount for each group
                  collectingAndThen(mapping(Orders::getDemands, Collectors.toList()),
                            demands -> demands.stream()
                                  .flatMap(List::stream)
                                  .map(Demands::getAmount)
                                  .reduce(BigDecimal.ZERO, BigDecimal::add)),
                  // create an object by previous results
                 (names, demandSum) -> new Detail(names.size(), demandSum) 
                    )
            )).entrySet().stream()
            .map(entry -> new Result(
                    entry.getKey().get(0),
                    entry.getKey().get(1),
                    entry.getValue().getCountByAccountName(),
                    entry.getValue().getDemandSum()))
            .collect(Collectors.toList());