Java 8 Collectors.groupingBy с отображенным значением, чтобы установить результат сбора в тот же набор
В примере используются объекты из пакета org.jsoup.nodes
import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
Мне нужны атрибуты группы по ключу с результирующим значением Set
.
Optional<Element> buttonOpt = ...;
Map<String, Set<String>> stringStringMap =
buttonOpt.map(button -> button.attributes().asList().stream()
.collect(groupingBy(Attribute::getKey,
mapping(attribute -> attribute.getValue(), toSet()))))
.orElse(new HashMap<>());
Кажется, что он собран правильно, но все время значение представляет собой одну строку (из-за реализации библиотеки), которая содержит различные значения, разделенные пробелом. Попытка улучшить решение:
Map<String, Set<HashSet<String>>> stringSetMap = buttonOpt.map(
button -> button.attributes()
.asList()
.stream()
.collect(groupingBy(Attribute::getKey,
mapping(attribute ->
new HashSet<String>(Arrays.asList(attribute.getValue()
.split(" "))),
toSet()))))
.orElse(new HashMap<>());
В результате у меня есть другая структура Map<String, Set<HashSet<String>>>
но мне нужно Map<String, Set<String>>
Я проверил некоторых коллекционеров, но не справился с моей проблемой.
Вопрос:
Как объединить все наборы, связанные с одним и тем же ключом атрибута?
Ответы
Ответ 1
Вы можете разделить свои атрибуты с помощью flatMap
и создать новые записи для группы:
Optional<Element> buttonOpt = ...
Map<String, Set<String>> stringStringMap =
buttonOpt.map(button ->
button.attributes()
.asList()
.stream()
.flatMap(at -> Arrays.stream(at.getValue().split(" "))
.map(v -> new SimpleEntry<>(at.getKey(),v)))
.collect(groupingBy(Map.Entry::getKey,
mapping(Map.Entry::getValue, toSet()))))
.orElse(new HashMap<>());
Ответ 2
Вот способ Java9 сделать это,
Map<String, Set<String>> stringSetMap = buttonOpt
.map(button -> button.attributes().asList().stream()
.collect(Collectors.groupingBy(Attribute::getKey, Collectors.flatMapping(
attribute -> Arrays.stream(attribute.getValue().split(" ")), Collectors.toSet()))))
.orElse(Collections.emptyMap());
Ответ 3
Это становится менее сложным, если вы используете для него более подходящую структуру данных, а именно мультикарту.
Мультикарты присутствуют, например, в Гуаве, где вы можете сделать это следующим образом:
SetMultimap<String, String> stringMultimap = buttonOpt
.map(button -> button.attributes().asList().stream()
.collect(ImmutableSetMultimap.flatteningToImmutableSetMultimap(
Attribute::getKey,
attribute -> Arrays.stream(attribute.getValue().split(" "))
))
).orElse(ImmutableSetMultimap.of());
Я сделал его неизменным ( ImmutableSetMultimap
), но изменяемая версия также может быть получена с помощью Multimaps.flatteningToMultimap
.