为了账号安全,请及时绑定邮箱和手机立即绑定

Java Streams - 按两个条件汇总结果分组

Java Streams - 按两个条件汇总结果分组

缥缈止盈 2023-06-04 15:23:14
我有一个订单列表,我应该按两个标准进行分组。Order_Id| Customer |    Date    | Amount |   1    | "Sam"    | 2019-03-21 | 100    |   2    | "Nick"   | 2019-03-21 | 102    |   3    | "Dan"    | 2019-03-21 | 300    |   4    | "Sam"    | 2019-04-21 | 400    |   5    | "Jenny"  | 2019-04-21 | 220    |   6    | "Jenny"  | 2019-04-12 | 330    |应该找到每个月总金额的最高买家,对于当前示例:{  MARCH: { customer='Dan', amount=300 },   APRIL: { customer='Jenny', amount=550 }}我找到了一个解决方案:public class Main {    public static void main(String[] args) {        List<Order> orders = List.of(                new Order(1L, "Sam", LocalDate.of(2019, 3, 21), 100L),                new Order(2L, "Nick", LocalDate.of(2019, 3, 21), 102L),                new Order(3L, "Dan", LocalDate.of(2019, 3, 21), 300L),                new Order(4L, "Sam", LocalDate.of(2019, 4, 21), 400L),                new Order(5L, "Jenny", LocalDate.of(2019, 4, 21), 220L),                new Order(6L, "Jenny", LocalDate.of(2019, 4, 12), 330L)        );        solution1(orders);    }     private static void solution1(List<Order> orders) {        final Map<Month, Map<String, Long>> buyersSummed = new HashMap<>();        for (Order order : orders) {            Map<String, Long> customerAmountMap = buyersSummed.computeIfAbsent(order.getOrderMonth(), mapping -> new HashMap<>());            customerAmountMap.putIfAbsent(order.getCustomer(), 0L);            Long customerAmount = customerAmountMap.get(order.getCustomer());            customerAmountMap.put(order.getCustomer(), customerAmount + order.getAmount());        }        final Map<Month, BuyerDetails> topBuyers = buyersSummed.entrySet().stream()                .collect(                        toMap(Entry::getKey, customerAmountEntry -> customerAmountEntry.getValue().entrySet().stream()                                .map(entry -> new BuyerDetails(entry.getKey(), entry.getValue()))                                .max(Comparator.comparingLong(BuyerDetails::getAmount)).orElseThrow())                );    }}
查看完整描述

6 回答

?
一只名叫tom的猫

TA贡献1906条经验 获得超3个赞

尝试使用groupingBy,summingLong如下comparingLong所示


Map<Month, BuyerDetails> topBuyers = orders.stream()

    .collect(Collectors.groupingBy(Order::getOrderMonth,

             Collectors.groupingBy(Order::getCustomer,

             Collectors.summingLong(Order::getAmount))))

    .entrySet().stream()

    .collect(Collectors.toMap(Map.Entry::getKey,

             order -> order.getValue().entrySet().stream()

            .max(Comparator.comparingLong(Map.Entry::getValue))

            .map(cust -> new BuyerDetails(cust.getKey(), cust.getValue())).get()));

输出


{

  "MARCH": { "customer": "Dan", "amount": 300 }, 

  "APRIL": { "customer": "Jenny", "amount": 550 }

}


查看完整回答
反对 回复 2023-06-04
?
繁星淼淼

TA贡献1775条经验 获得超11个赞

有没有办法在一个流中解决上述任务?

这取决于你所说的“在一个流中”是什么意思。您想要执行一个缩减操作,该操作的最佳特征可能是一系列缩减的组合:

  • 按月对订单进行分组

  • 在每个月组内,汇总每个客户的订单以产生总金额

  • 在每个月的每个客户聚合结果组中,选择数量最大的一个(注意:在关系的情况下没有明确定义)

从 Stream API 的角度来看,对流执行这些单独的归约中的任何一个都是对该流的终端操作。您可以使用新流处理结果,甚至在语法上将其链接在一起,但尽管这可能采用单个方法调用的语法形式,但它不会构成在单个流上发生的所有操作。

您还可以创建一个Collector(或一个的组件),以便通过收集输入元素流直接获得结果,但在内部,该收集器仍需要通过内部创建和消耗额外的来执行单独的归约流,或通过非流 API 执行相同的任务。如果您再次计算这些内部操作,不,它不构成对单个流的执行操作。(但是,如果您不考虑这些内部缩减,那么是的,这一切都在一个流中完成。)


查看完整回答
反对 回复 2023-06-04
?
长风秋雁

TA贡献1757条经验 获得超7个赞

好吧,我们开始吧!以下代码将为您提供所需的内容,只需调用 1 次即可stream():


Map<Month, BuyerDetails> grouped = orders.stream().collect(

  Collectors.groupingBy(Order::getOrderMonth,

    Collectors.collectingAndThen(

      Collectors.groupingBy(Order::getCustomer,

        Collectors.summingLong(Order::getAmount)

      ),

      ((Function<Map<String,Long>, Map.Entry<String,Long>>) 

        map -> Collections.max(

          map.entrySet(), Comparator.comparingLong(Map.Entry::getValue)

        )

      ).andThen(

        e -> new BuyerDetails(e.getKey(),e.getValue())

      )

    )

  )

);

System.out.println(grouped);

输出:


{MARCH={ customer='Dan', amount=300 }, APRIL={ customer='Jenny', amount=550 }}


现在,这有点奇怪,所以让我们逐行检查它,看看发生了什么:


Map<Month, BuyerDetails> grouped = orders.stream().collect(

首先,我们流式传输我们的订单,


  Collectors.groupingBy(Order::getOrderMonth,

按月分组,我们发现:


    Collectors.collectingAndThen(

      Collectors.groupingBy(Order::getCustomer,

每个客户和


        Collectors.summingLong(Order::getAmount)

      ),

他们一个月内的总订单。


      ((Function<Map<String,Long>, Map.Entry<String,Long>>)

(我们转换为Function,所以我们可以使用andThen我们定义的 lambda 函数之类的方法)


        map -> Collections.max(

          map.entrySet(), Comparator.comparingLong(Map.Entry::getValue)

        )

对于每个月,我们都会找到具有最大订单金额的客户。


      ).andThen(

然后我们


        e -> new BuyerDetails(e.getKey(),e.getValue())

为所述客户创建新的买家详细信息


      )

    )

  )

);

并收集所有 Month/BuyerDetail 对。


System.out.println(grouped);

最后,我们打印创建的数据结构。


查看完整回答
反对 回复 2023-06-04
?
慕娘9325324

TA贡献1783条经验 获得超4个赞

它有一个嵌套流,所以它不是一个流,它返回Map<String, Optional<BuyerDetails>>。



orders.stream()

        .collect(

            Collectors.groupingBy(Order::getOrderMonth,

                Collectors.collectingAndThen(

                        Collectors.groupingBy(

                                Order::getCustomer,

                                Collectors.summarizingLong(Order::getAmount)

                        ),

                        e -> e.entrySet()

                                .stream()

                                .map(entry -> new BuyerDetails(entry.getKey(), entry.getValue().getSum()))

                                .max(Comparator.comparingLong(BuyerDetails::getAmount))

                )

            )

        )

所以有3个步骤:


按月分组Collectors.groupingBy(Order::getOrderMonth,

按客户名称分组并汇总总订单金额Collectors.groupingBy(Order::getCustomer, Collectors.summarizingLong( Order::getAmount))

过滤中间结果,只留下最大金额的客户max(Comparator.comparingLong(BuyerDetails::getAmount))

输出是


{

  APRIL = Optional [ BuyerDetails { customer = 'Jenny', amount = 550 } ],

  MARCH = Optional [ BuyerDetails { customer = 'Dan', amount = 300 } ]

}

我也很好奇这是否可以在没有额外流的情况下完成。


查看完整回答
反对 回复 2023-06-04
?
www说

TA贡献1775条经验 获得超8个赞

我的方法(3 个流):


private static void solution1(List<Order> orders) {

        final Map<Month, BuyerDetails> topBuyers = orders.stream().collect(

                Collectors.groupingBy(order -> order.getCustomer() + "$" + order.getOrderDate().getYear() + "." +

                                order.getOrderMonth(),

                        Collectors.reducing((ord1, ord2) -> {

                            ord1.setAmount(ord1.getAmount() + ord2.getAmount());

                            return ord1;

                        }))).values().stream()

                .collect(Collectors.groupingBy(order -> order.get().getOrderMonth(),

                        maxBy(Comparator.comparing(order -> order.get().getAmount())))).values().stream()

                .collect(

                        toMap((key) -> key.get().get().getOrderMonth(),

                                key -> new BuyerDetails(key.get().get().getCustomer(), key.get().get().getAmount())

                        )

                );

    }


查看完整回答
反对 回复 2023-06-04
?
largeQ

TA贡献2039条经验 获得超7个赞

这不能用单个流来完成,因为 和sum都是max终端操作,它们不能应用于同一个流。最好把它分成两个操作


Map<Month, Map<String, Long>> sumsByMonth = orders.stream().collect(

        Collectors.groupingBy(

            Order::getOrderMonth,

            Collectors.groupingBy(

                    Order::getCustomer,

                    Collectors.mapping(

                            Order::getAmount,

                            Collectors.reducing(0L, a -> a, (a1, a2) -> a1 + a2)

                    )

            )

        )

);


Map<Month, BuyerDetails> topBuyers = sumsByMonth.entrySet().stream().collect(

        Collectors.toMap(

                Map.Entry::getKey,

                sums -> sums.getValue().entrySet().stream()

                        .max(Comparator.comparingLong(Map.Entry::getValue))

                        .map(e -> new BuyerDetails(e.getKey(), e.getValue()))

                        .get()

       )

);


查看完整回答
反对 回复 2023-06-04
  • 6 回答
  • 0 关注
  • 223 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信