#java #java-stream
Вопрос:
У меня есть сущность, которая выглядит так:
public class Snippet {
private Integer docId;
private Integer page;
private Payload payload;
}
Входные данные представляют собой List<Snippet>
Мне нужно создать индекс, который позволит нам перебирать идентификаторы документов и страницы и получать связанные объекты фрагментов.
Итак, структура данных, подобная этой:
Map<Integer, Map<Integer, List<Snippet>>>
Я могу использовать потоки Java для получения карты<Целое число, Карта<Целое число, Фрагмент><Целое число, фрагмент>> — но это не приведет к сбору списка фрагментов в конце.
List<Snippet> input = ....;
input.stream()
.collect(Collectors.groupingBy(
Snippet::getDocId,
Collectors.toMap(Snippet::getPage, Function.identity())
)
);
Как я могу собрать данные, чтобы получить список в качестве конечного значения карты?
Ответ №1:
Как вы можете видеть, Collectors.groupingBy(..)
получает коллектор в качестве 2-го аргумента. Итак, просто используйте Collectors.groupingBy(..)
в качестве 2-го аргумента Collectors.groupingBy(..)
input.stream().collect(Collectors.groupingBy(
Snippet::getDocId,
Collectors.groupingBy(Snippet::getPage,
Collectors.toList())));
Комментарии:
1. Спасибо — теперь, когда я это прочитал, это кажется таким очевидным. Я думаю, что я зациклился на необходимости собрать карту в карту — но я думаю, что коллектор groupingBy сам создает карту.
Ответ №2:
Вот еще одна альтернатива для вашего текущего или будущего рассмотрения. Он использует computeIfAbsent
метод интерфейса карты.
- создайте окончательную карту содержимого.
- выполните итерацию по списку
Snippets
- если
DocId
этого нет, создайте запись сDocId
ключом as. Затем попробуйте сохранить иpage
Snippet
- если
page
этого нет, создайте запись сpage
ключом as. Затем сохранитеSnippet
его в списке.
Вот пример использования записей вместо классов.
record PayLoad(String getValue) {
@Override
public String toString() {
return "[" getValue() "]";
}
}
record Snippet(int getDocId, int getPage,
PayLoad getPayLoad) {
@Override
public String toString() {
return String.format("{%s, %s, %s}", getDocId,
getPage, getPayLoad);
}
}
Создайте некоторые данные
List<Snippet> input =
List.of(new Snippet(1, 1, new PayLoad("A")),
new Snippet(1, 2, new PayLoad("B")),
new Snippet(1, 3, new PayLoad("C")),
new Snippet(2, 1, new PayLoad("D")),
new Snippet(2, 2, new PayLoad("E")),
new Snippet(2, 3, new PayLoad("F")));
Сохраните фрагменты
Map<Integer, Map<Integer, List<Snippet>>> map =
new HashMap<>();
for (Snippet s : input) {
map.computeIfAbsent(s.getDocId(), v -> new HashMap<>())
.computeIfAbsent(s.getPage(),
v -> new ArrayList<>())
.add(s);
}
Распечатайте их
map.forEach((k, v) -> {
System.out.println(k);
v.forEach((kk, vv) -> System.out
.println(" " kk " -> " vv));
});
С принтами
1
1 -> [{1, 1, [A]}]
2 -> [{1, 2, [B]}]
3 -> [{1, 3, [C]}]
2
1 -> [{2, 1, [D]}]
2 -> [{2, 2, [E]}]
3 -> [{2, 3, [F]}]
Комментарии:
1. Минимальная версия Java 17?
2. Нисколько. Записи были сделаны просто для упрощения примера, но были доработаны в Java 16. Карта является Java 8, поэтому фактический производственный запуск будет работать с Java 8 .
3. Это очень умное решение — спасибо. computeIfAbsent создает очень элегантный код для случаев, которые слишком сложны для потоковой операции. Спасибо вам за то, что делаете все возможное и невозможное — это ценится.