0

I'd like to know how I can insert a map into another map using streams in java.

I have a two maps

    Map<String, List<Character>> map1

    Map<String, List<Integer>> map2

I d like to merge both maps such that we have

    Map<String, Map<Character, Integer>> finalmap

if map1 is something like

    {String1 = [Character1, Character2], String2 = [Character3, Character4], etc}

and map2 is

    {String1 = [Integer1, Integer2], String2 = [Integer3, Integer4], etc}

I want it to merge such that the innermap maps Character1 with Integer1 and so on. Does someone have an idea how to solve this problem? :)

2 Answers2

1
Map<String, Map<Character, Integer>> map3 = map1.entrySet()
                .stream()
                .flatMap(entry -> {
                    if (map2.containsKey(entry.getKey())) {
                        List<Integer> integers = map2.get(entry.getKey());
                        List<Character> characters = entry.getValue();
                        Map<Character, Integer> innerMap = IntStream.range(0, Math.min(integers.size(), characters.size()))
                                .mapToObj(i -> Map.entry(characters.get(i), integers.get(i)))
                                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

                        return Stream.of(Map.entry(entry.getKey(), innerMap));
                    }

                    return Stream.empty();
                })
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Andrew Vershinin
  • 1,958
  • 11
  • 16
1

This is a bit late but in case the length of Character and Integer lists is different, it may be possible to build an inner map containing all Character keys and null for missing Integer values:

// class MyClass
static Map<String, Map<Character, Integer>> joinMaps(
    Map<String, List<Character>> map1, Map<String, List<Integer>> map2) 
{
    return map1
            .entrySet()
            .stream()
            .filter(e -> map2.containsKey(e.getKey())) // keep the keys from both maps
            .map(e -> Map.entry(
                e.getKey(), // String key for result
                IntStream.range(0, e.getValue().size()) // build map <Character, Integer>
                         .mapToObj(i -> new AbstractMap.SimpleEntry<>(
                             e.getValue().get(i), 
                             i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
                         ))
                         // use custom collector to allow null Integer values
                         .collect(
                             MyClass::innerMap, 
                             (hm, e2) -> hm.put(e2.getKey(), e2.getValue()),
                             Map::putAll
                         )
            ))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
    
static LinkedHashMap<Character, Integer> innerMap() {
    return new LinkedHashMap<>();
}

Custom collector for inner map is needed to allow adding null values which is not possible with Collectors.toMap where NPE is thrown.

Tests:

Map<String, List<Character>> map1 = Map.of(
    "S0", Arrays.asList('#', '@'),
    "S1", Arrays.asList('a', 'b'),
    "S2", Arrays.asList('c', 'd'),
    "S3", Arrays.asList('e', 'f')
);
System.out.println("map1=" + map1);

Map<String, List<Integer>> map2 = Map.of(
    "S1", Arrays.asList(1),
    "S2", Arrays.asList(3, 4, 5),
    "S3", Arrays.asList(5, 6),
    "S4", Arrays.asList(7, 8)
);
System.out.println("map2=" + map2);

Map<String, Map<Character, Integer>> res = joinMaps(map1, map2);       
System.out.println("----\nResult:");
res.forEach((k, v) -> System.out.printf("%s -> %s%n", k, v));

Output:

map1={S0=[#, @], S1=[a, b], S2=[c, d], S3=[e, f]}
map2={S1=[1], S2=[3, 4, 5], S3=[5, 6], S4=[7, 8]}
----
Result:
S1 -> {a=1, b=null}
S2 -> {c=3, d=4}
S3 -> {e=5, f=6}

Update
Another solution using Stream::flatMap and groupingBy + mapping collectors with a custom collector used as a downstream collector is shown below:

static Map<String, Map<Character, Integer>> joinMaps(
    Map<String, List<Character>> map1, Map<String, List<Integer>> map2) 
{
    return map1
        .entrySet()
        .stream()
        .filter(e -> map2.containsKey(e.getKey()))
        .flatMap(e -> IntStream.range(0, e.getValue().size())
            .mapToObj(i -> Map.entry(
                e.getKey(),
                new AbstractMap.SimpleEntry<>(
                    e.getValue().get(i), 
                    i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
                )
        ))) // Stream<Map.Entry<String, Map.Entry<Character, Integer>>>
        .collect(Collectors.groupingBy(
            Map.Entry::getKey,  // use String as key in outer map
            Collectors.mapping(e -> e.getValue(), // build inner map
                Collector.of(                     // using custom collector
                    MyClass::innerMap, // supplier
                    (hm, e2) -> hm.put(e2.getKey(), e2.getValue()), // accumulator
                    MyClass::mergeMaps // combiner
            ))
        ));
}

static <T extends Map> T mergeMaps(T acc, T map) {
    acc.putAll(map);
    return acc;
}

Here mergeMaps is a BinaryOperator<A> combiner argument in the method Collector.of which slightly differs from Stream::collect used above where BiConsumer<R, R> is used as combiner.

Nowhere Man
  • 19,170
  • 9
  • 17
  • 42