#java #java-stream
Вопрос:
У меня есть структура, подобная этой:
Map<KeyType1, Map<KeyType2, List<ValueType>>>
А также класс, содержащий как KeyType1, так и KeyType2, назовем его AggregatedKey. Его можно создать с помощью конструктора:
public AggregatedKey(KeyType1 keyType1, KeyType2 keyType2)
Моя цель-сопоставить приведенную выше структуру с чем-то вроде:
Map<AggregatedKey, List<ValueType>>
Таким образом, в основном ключи должны быть сопоставлены с одним агрегированным ключом.
Как я могу добиться этого с помощью Java 9?
Комментарии:
1. Есть ли у вас функция, которая строит агрегированный ключ из заданных двух ключей?
2. Они могут быть объединены с помощью конструктора: AggregatedKey(KeyType1, KeyType2)
Ответ №1:
Это сделает свое дело
Map<KeyType1, Map<KeyType2, List<String>>> m = new HashMap<>();
Map<AggregatedKey, List<String>> result = new HashMap<>();
m.entrySet().forEach(entry -> {
entry.getValue().entrySet().forEach(nestedEntry -> {
result.put(new AggregatedKey(entry.getKey(), nestedEntry.getKey()), nestedEntry.getValue());
});
});
Не забудьте внедрить hashcode/equals
в свой AggregatedKey
, иначе у вас возникнут некоторые проблемы с использованием result
карты.
Комментарии:
1. Я отметил это как принятое, поскольку нахожу его самым коротким, хотя лично мне не нравится создавать внешний агрегатор (как результат здесь), его легче понять.
2. @Anakin001 На самом деле, это немного другая версия
for...each
. В случае, если вы используете поток, вы должны получить результат вместо обновления созданной коллекции.3. Вы правы! Хотя все и так прекрасно, все ясно, и это работает (я только что проверил это).
Ответ №2:
Вы можете сделать это так, используя потоки.
- первый поток набора записей внешней карты
- затем вызовите
flatMap
для потоковой передачи внутреннюю картуentrySet
- создайте
AggregatedKey
экземпляр с помощьюouterEntry.getKey()
иinnerEntry.getKey()
обратите внимание, что для этого требуется, чтобы у этого класса был конструктор, принимающий ключи. - затем поместите этот экземпляр и значение из внутренней карты (
List<ValueType>
) вAbstractMap.SimpleEntry
экземпляр для передачи сборщику. - создайте новую карту с ключом и значением
SimpleEntry
Дана следующая исходная карта.
Map<KeyType1, Map<KeyType2, List<ValueType>>> map =
new HashMap<>(); // contains the info to be remapped.
Вот результат
Map<AggregatedKey, List<ValueType>> result = map.entrySet()
.stream()
.flatMap(outerEntry-> outerEntry
.getValue().entrySet().stream()
.map(innerEntry -> new AbstractMap.SimpleEntry<>(
new AggregatedKey(outerEntry.getKey(),innerEntry.getKey()),
innerEntry.getValue())))
.collect(Collectors.toMap(
AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue));
}
Комментарии:
1. Это самый «красивый» ответ, так как он просто применяет потоковые операции и получает результат, жаль, что Stackoverflow позволяет мне выбрать только один принятый ответ 🙁
Ответ №3:
Это один из способов:
public static void main(String[] args) {
Map<String, Map<String, String>> map = new HashMap<>();
Map<String, String> innerMap1 = new HashMap<>();
Map<String, String> innerMap2 = new HashMap<>();
innerMap1.put("k11", "v11");
innerMap1.put("k12", "v12");
innerMap1.put("k13", "v13");
innerMap2.put("k21", "v22");
innerMap2.put("k22", "v22");
map.put("k1", innerMap1);
map.put("k2", innerMap2);
Map<String, String> result = map
.entrySet()
.stream()
.flatMap(stringMapEntry ->
stringMapEntry
.getValue()
.entrySet()
.stream()
.map(stringStringEntry ->
new AbstractMap.SimpleEntry<String, String>(
buildAggregatedKey(
stringMapEntry.getKey(),
stringStringEntry.getKey()
),
stringStringEntry.getValue()
)
)
).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
System.out.println(result);
}
private static String buildAggregatedKey(String key1, String key2){
return key1 "_" key2;
}
Где вы изменяете это buildAggregatedKey
в соответствии с вашей логикой агрегирования.
Ответ №4:
это пример теста с использованием потоков, где на первом этапе он преобразует внутренний элемент карты, а на втором собирает на карту:
package prove.aggregatemap;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TestAggregator {
@Test
public void aggregate() {
Map<String, List<String>> letter_map= Map.of("first", List.of("one","two","three"),
"second", List.of("four","five","six"),
"third", List.of("seven","eight","nine"));
Map<String, List<String>> num_map= Map.of("first_num", List.of("1","2","3"), "second_num", List.of("4","5","6"), "third_num", List.of("7","8","9"));
Map<String,Map<String,List<String>>> mapOfMaps=Map.of("letter",letter_map,"num",num_map);
Map<AggregateKey, List<String>> result=mapOfMaps.entrySet().stream().flatMap(entry ->
entry.getValue().entrySet().stream().collect(Collectors.toMap(
inner_entry -> new AggregateKey(entry.getKey(), inner_entry.getKey()),
inner_entry -> inner_entry.getValue())).entrySet().stream()
).collect(Collectors.toMap(entry->entry.getKey(),entry->entry.getValue()));
Assert.assertEquals(List.of("one","two","three"),result.get(new AggregateKey("letter","first")));
Assert.assertEquals(List.of("four","five","six"),result.get(new AggregateKey("letter","second")));
Assert.assertEquals(List.of("seven","eight","nine"),result.get(new AggregateKey("letter","third")));
}
}
Ответ №5:
public static final class AggregatedKey<K1, K2> {
private final K1 one;
private final K2 two;
public AggregatedKey(K1 one, K2 two) {
this.one = one;
this.two = two;
}
}
public static <K1, K2, V> Map<AggregatedKey<K1, K2>, List<V>> convert1(Map<K1, Map<K2, List<V>>> map) {
Map<AggregatedKey<K1, K2>, List<V>> res = new HashMap<>();
for (Map.Entry<K1, Map<K2, List<V>>> one : map.entrySet())
for (Map.Entry<K2, List<V>> two : one.getValue().entrySet())
res.put(new AggregatedKey<>(one.getKey(), two.getKey()), two.getValue());
return res;
}
public static <K1, K2, V> Map<AggregatedKey<K1, K2>, List<V>> convert2(Map<K1, Map<K2, List<V>>> map) {
return map.entrySet().stream()
.flatMap(e1 -> e1.getValue().entrySet().stream()
.map(e2 -> new AggregatedKey<>(new AggregatedKey<>(e1.getKey(), e2.getKey()), e2.getValue())))
.collect(Collectors.toMap(tuple -> tuple.one, tuple -> tuple.two));
}
Комментарии:
1. Забавно, как в последнее время я склонен мыслить в терминах потоков, хотя иногда классика для может работать лучше. На мой взгляд, это, безусловно, самое простое решение для понимания, но я отметил ответ Пилпо как принятый, поскольку в данном случае я хотел получить решение streams.
2. @Anakin001 смотрите мой обновленный ответ. Но я думаю, что
converter1()
это проще.3. @Anakin001, но я отметил ответ Пилпо как принятый, так как в этом случае я хотел получить решение для потоков. Тогда почему вы приняли другой ответ? Здесь не было никаких потоков.
4. @WJS действительно, это была ошибка, я хотел сказать «функциональное программирование», и в итоге я сказал «потоки» (технически, это была ошибка чтения в первую очередь, я не видел, чтобы решение вызывало «forEach» без потоковой передачи набора записей в первую очередь, но, тем не менее, я действительно хотел иметь более «функциональное» решение).