Преобразовать карту <String, Object> в карту <String, Set <Object >> с фильтром и потоками
Я хотел бы преобразовать мою карту, которая выглядит следующим образом:
{
key="someKey1", value=Apple(id="1", color="green"),
key="someKey2", value=Apple(id="2", color="red"),
key="someKey3", value=Apple(id="3", color="green"),
key="someKey4", value=Apple(id="4", color="red"),
}
на другую карту, которая помещает все яблоки одного цвета в один и тот же список:
{
key="red", value=list={apple1, apple3},
key="green", value=list={apple2, apple4},
}
Я попробовал следующее:
Map<String, Set<Apple>> sortedApples = appleMap.entrySet()
.stream()
.collect(Collectors.toMap(l -> l.getColour, ???));
Я на правильном пути? Должен ли я использовать фильтры для этой задачи? Есть ли более простой способ?
Ответы
Ответ 1
если вы хотите продолжить работу с toMap
вы можете получить результат следующим образом:
map.values() // get the apples
.stream() // Stream<Apple>
.collect(toMap(Apple::getColour, // group by colour
v -> new HashSet<>(singleton(v)), // have values as set of apples
(l, r) -> {l.addAll(r); return l;})); // merge colliding apples by colour
- поток по
values
карты вместо entrySet
потому что нас не интересуют ключи карты. -
Apple::getColour
- это функция keyMapper
используемая для извлечения "вещи", которую мы хотим сгруппировать, в данном случае по цвету Apple
. -
v → new HashSet<>(singleton(v))
- функция valueMapper
используемая для результирующих значений карты -
(l, r) → {l.addAll(r); return l;}
(l, r) → {l.addAll(r); return l;}
- это функция слияния, используемая для объединения двух HashSet
когда происходит коллизия клавиш в цвете Apple
. - наконец, итоговая карта представляет собой
Map<String, Set<Apple>>
но это лучше с groupingBy
и toSet
как downstream:
map.values().stream().collect(groupingBy(Apple::getColour, toSet()));
-
поток по values
карты вместо entrySet
потому что нас не интересуют ключи карты.
-
группирует Apple
по предоставленной функции классификации, то есть Apple::getColour
а затем собирает значения в наборе, следовательно, в нисходящий коллектор toSet
.
-
наконец, итоговая карта представляет собой Map<String, Set<Apple>>
краткий, читаемый и идиоматический подход.
Вы также можете сделать это без потока:
Map<String, Set<Apple>> res = new HashMap<>();
map.values().forEach(a -> res.computeIfAbsent(a.getColour(), e -> new HashSet<>()).add(a));
- итерируйте по
values
карты вместо entrySet
потому что нас не интересуют ключи карты. - если указанный ключ
a.getColour()
еще не связан со значением, он пытается вычислить его значение, используя заданную функцию отображения e → new HashSet<>()
и вводит его в карту. Затем мы добавляем Apple
в результирующий набор. - если указанный ключ
a.getColour()
уже связан со значением, computeIfAbsent
возвращает существующее значение, связанное с ним, и затем мы вызываем add(a)
в HashSet
чтобы ввести Apple
в набор. - наконец, итоговая карта представляет собой
Map<String, Set<Apple>>
Ответ 2
Collectors.groupingBy
более подходит для этой задачи, чем Collectors.toMap
(хотя можно использовать и то, и другое).
Map<String, List<Apple>> sortedApples =
appleMap.values()
.stream()
.collect(Collectors.groupingBy(Apple::getColour));
Или сгруппировать их в Set
:
Map<String, Set<Apple>> sortedApples =
appleMap.values()
.stream()
.collect(Collectors.groupingBy(Apple::getColour,
Collectors.mapping(Function.identity(),
Collectors.toSet())));
или (как прокомментировал Аомин):
Map<String, Set<Apple>> sortedApples =
appleMap.values()
.stream()
.collect(Collectors.groupingBy(Apple::getColour, Collectors.toSet()));
Ответ 3
Вы можете использовать Collectors.groupingBy
и Collectors.toSet()
Map<String, Set<Apple>> sortedApples = appleMap.values() // Collection<Apple>
.stream() // Stream<Apple>
.collect(Collectors.groupingBy(Apple::getColour, // groupBy colour
Collectors.mapping(a -> a, Collectors.toSet()))); // collect to Set
Ответ 4
Вы спросили, как сделать это с потоками, но здесь есть другой способ:
Map<String, Set<Apple>> result = new LinkedHashMap<>();
appleMap.values().forEach(apple ->
result.computeIfAbsent(apple.getColor(), k -> new LinkedHashSet<>()).add(apple));
При этом используется Map.computeIfAbsent
, который либо возвращает набор, сопоставленный этому цвету, либо помещает пустой LinkedHashSet
в карту, если еще ничего не сопоставлено с этим цветом, затем добавляет яблоко в набор.
РЕДАКТИРОВАТЬ: я использую LinkedHashMap
и LinkedHashSet
чтобы сохранить порядок вставки, но мог бы использовать HashMap
и HashSet
, соответственно.