Работа с вложенными картами с помощью Stream API - PullRequest
0 голосов
/ 31 января 2019

Мой текущий подход, использующий Streams 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 Может быть, может быть полезным?

Ответы [ 2 ]

0 голосов
/ 12 февраля 2019

Это должно сделать;)

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

}

Pair - это org.apache.commons.lang3.tuple.Pair.Если вы не хотите использовать библиотеку Appache Commons, вместо этого вы можете использовать java.util.AbstractMap.SimpleEntry.

0 голосов
/ 10 февраля 2019

Вы были в значительной степени обречены в тот момент, когда сгруппировались по клиенту.Верхний уровень 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...