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