使用Stream API处理嵌套地图

时间:2019-01-31 16:03:12

标签: java java-stream

我目前使用结合StreamStream API和forEach循环的方法:

public Map<String, Client> clientsWithMostPurchasesInEachCategory(Map<Client, Map<Product,Integer>> shopping) {

    Map<String, Client> result = new HashMap<>();

    Map<Client, Map<String, BigDecimal>> temp =
            shopping.entrySet()
                    .stream()
                    .collect(Collectors.groupingBy(Map.Entry::getKey,
                             Collectors.flatMapping(e -> e.getValue().entrySet().stream(),
                             Collectors.groupingBy(e -> e.getKey().getCategory(),
                             Collectors.mapping(ee -> ee.getKey().getPrice().multiply(BigDecimal.valueOf(ee.getValue())),
                             Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))))));

    /*curious, how could I refactor that piece of code, so the method uses only one stream chain? */
    temp.forEach((client, value) 
        -> value.forEach((category, value1) 
        -> {
               if (!result.containsKey(category) ||
                   temp.get(result.get(category)).get(category).compareTo(value1) < 0)
                   result.put(category, client);
           }));    

    return result;

}

建议使用该方法的名称,我想找到一张地图Map <String, Client>,其中包含每种产品类别中指定类别(作为键)中购买量最大(价值)的客户

购物基本上是一张地图:Map<Client, Map<Product,Integer>>

  • 外键代表客户
  • 内部键代表产品。产品类别的成员为名称,类别,价格(BigDecimal)
  • 内部地图值(整数)代表属于特定客户的指定产品的数量

不确定,是否有可能? Collectors.collectingAndThen可能有用吗?

3 个答案:

答案 0 :(得分:1)

您可以使用 StreamEx 库,并这样做

public static Map<String, Client> clientsWithMostPurchasesInEachCategory(Map<Client, Map<Product, Integer>> shopping) {
        return EntryStream.of(shopping)
                .flatMapKeyValue(((client, productQuantityMap) ->
                        EntryStream.of(productQuantityMap)
                                .mapToValue((p, q) -> p.getPrice().multiply(BigDecimal.valueOf(q)))
                                .mapKeys(Product::getCategory)
                                .map(e -> new ClientCategorySpend(client, e.getKey(), e.getValue())))
                )
                .groupingBy(
                        ClientCategorySpend::getCategory,
                        Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(ClientCategorySpend::getSpend)),
                                t -> t.get().getClient())
                );
    }

答案 1 :(得分:0)

按客户分组的那一刻,您几乎注定要失败。的 顶层Collectors.groupingBy必须使用类别作为按关键字进行分组。

要做到这一点,您需要先flatMap再收集,这样您才能获得固定的 客户+类别+支出元素。

这是一种方法。我首先为展平流的元素定义一个POJO:

  static class ClientCategorySpend
  {
      private final Client client;
      private final String category;
      private final BigDecimal spend;

      public ClientCategorySpend(Client client, String category, BigDecimal spend)
      {
          this.client = client;
          this.category = category;
          this.spend = spend;
      }

      public String getCategory()
      {
          return category;
      }

      public Client getClient()
      {
          return client;
      }

      public BigDecimal getSpend()
      {
          return spend;
      }
  }

现在是功能:

public static Map<String, Client> clientsWithMostPurchasesInEachCategory(Map<Client, Map<Product, Integer>> shopping)
{
     // <1>
     Collector<? super ClientCategorySpend, ?, BigDecimal> sumOfSpendByClient = Collectors.mapping(ClientCategorySpend::getSpend,
             Collectors.reducing(BigDecimal.ZERO, BigDecimal::add));


     // <2>
     Collector<? super ClientCategorySpend, ?, Map<Client, BigDecimal>> clientSpendByCategory = Collectors.groupingBy(
             ClientCategorySpend::getClient,
             sumOfSpendByClient
     );

     // <3>
     Collector<? super ClientCategorySpend, ?, Client> maxSpendingClientByCategory = Collectors.collectingAndThen(
             clientSpendByCategory,
             map -> map.entrySet().stream()
                     .max(Comparator.comparing(Map.Entry::getValue))
                     .map(Map.Entry::getKey).get()
     );

     return shopping.entrySet().stream()
            // <4>
             .flatMap(
                     entry -> entry.getValue().entrySet().stream().map(
                             entry2 -> new ClientCategorySpend(entry.getKey(),
                                     entry2.getKey().category,
                                     entry2.getKey().price.multiply(BigDecimal.valueOf(entry2.getValue())))
                     )
             ).collect(Collectors.groupingBy(ClientCategorySpend::getCategory, maxSpendingClientByCategory));
}

一旦有了ClientCategorySpend(4)的流,就可以按类别对其进行分组。我用 clientSpendByCategory收集器(2),以在客户和类别中的总支出之间建立映射。反过来,这取决于sumToSpendByClient(1),它基本上是减少费用的减速器。然后,您可以按照建议使用collectingAndThen, 使用Map<Client, BigDecimal>将每个max减少到单个客户端。

答案 2 :(得分:0)

这应该做;)

public Map<String, Client> clientsWithMostPurchasesInEachCategory(Map<Client, Map<Product, Integer>> shopping) {

    return shopping
            .entrySet()
            .stream()
            .map(entry -> Pair.of(
                    entry.getKey(),
                    entry.getValue()
                            .entrySet()
                            .stream()
                            .map(e -> Pair.of(
                                    e.getKey().getCategory(),
                                    e.getKey().getPrice().multiply(
                                            BigDecimal.valueOf(e.getValue()))))
                            .collect(Collectors.toMap(
                                    Pair::getKey,
                                    Pair::getValue,
                                    BigDecimal::add))))

            // Here we have: Stream<Pair<Client, Map<String, BigDecimal>>>
            // e.g.: per each Client we have a map { category -> purchase value }

            .flatMap(item -> item.getValue()
                    .entrySet()
                    .stream()
                    .map(e -> Pair.of(
                            e.getKey(), Pair.of(item.getKey(), e.getValue()))))

            // Here: Stream<Pair<String, Pair<Client, BigDecimal>>>
            // e.g.: entries stream { category, { client, purchase value } }
            // where there are category duplicates, so we must select those  
            // with highest purchase value for each category.

            .collect(Collectors.toMap(
                    Pair::getKey,
                    Pair::getValue,
                    (o1, o2) -> o2.getValue().compareTo(o1.getValue()) > 0 ?
                            o2 : o1))

            // Now we have: Map<String, Pair<Client, BigDecimal>>,
            // e.g.: { category -> { client, purchase value } }
            // so just get rid of unnecessary purchase value...

            .entrySet()
            .stream()
            .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    e -> e.getValue().getKey()));

}

Pairorg.apache.commons.lang3.tuple.Pair。如果您不想使用Appache Commons库,则可以改用java.util.AbstractMap.SimpleEntry