[Java 8+] Merge maps

things to do

Consider merging multiple maps into one map. Eventually it will be generalized to the following utility methods.

// Map<K, V>[]Map<K, V>Merge into
public static <K, V> Map<K, V> merge(BiFunction<? super V, ? super V, ? extends V> mergeFunction, Map<K, V>... maps)

// Map<K, V>[]Map<K, R>Merge into
public static <K, V, R> Map<K, R> merge(Collector<V, ?, R> mergeCollector, Map<K, V>... maps)

// Map<K, List<V>>[]Map<K, List<V>>Merge into
public static <K, V> Map<K, List<V>> merge(Map<K, List<V>>... maps)

basic way of thinking

Expand Map <K, V> [] toStream <Entry <K, V >>and collect it, but if there are multiple entries with matching keys, you have to think about it on a case-by-case basis. Must be. The following two patterns can be roughly divided.

--Merge using Collectors # toMap (): Repeat processing and storing binary values with matching keys with the merge function --Merge using Collectors # grounpingBy (): Batch key matching values in lower collectors

If there are only two Maps to be merged, you can still do either, so we recommend using a simple Collectors # toMap ().

Merge using Collectors # toMap ()

Similar to Map # merge (), Collectors # toMap () can be used if you can merge by repeating the operation of merging a new value into an already stored value.

Map<String, Integer> m1 = new HashMap<>();
m1.put("a", 1);
m1.put("b", 2);
m1.put("c", 3);
Map<String, Integer> m2 = new HashMap<>();
m2.put("a", 100);
m2.put("b", 200);
m2.put("c", 300);

//If the keys match, take the sum of the values
Map<String, Integer> m3 =
    Stream.of(m1, m2)
          .flatMap(m -> m.entrySet().stream())
          .collect(Collectors.toMap(Entry::getKey, Entry::getValue, Integer::sum));

The third argument of Map # merge () is BiFunction, while the third argument of Collectors # toMap () is BinaryOperator (of the subinterface), which has some restrictions, but it shouldn't be a problem. If the BiFunction already passed to Map # merge () exists, it can be applied as it is by referring to BiFunction # apply ().

// Map#merge()3rd argument type of
BiFunction<? super V, ? super V, ? extends V> remappingFunction = ...;

// Collectors#toMap()Converted to the type of the third argument of
BinaryOperator<V> mergeFunction = remappingFunction::apply;

Merge using Collectors # grounpingBy ()

If there are 3 or more values for the same key and you want to use the average of them as the merged value, the above method will cause an error. Also, since the interface is BinaryOperator, it is not possible to perform aggregation in which the original value and the resulting value have different types. In such a case, you need to use Collectors # groupingBy () to store the value for each key in the collection and convert it to the result at the finisher.

//Average the values if the keys match
Map<String, Double> m4 =
    Stream.of(m1, m2)
          .flatMap(m -> m.entrySet().stream())
          .collect(Collectors.groupingBy(Entry::getKey, Collectors.averagingInt(Entry::getValue)));

Example of merging Map <K, List <V >>

As a more concrete example, let's merge the data in Map <K, List <V >> format by the above two methods.

Map<String, List<String>> lm1 = new HashMap<>();
lm1.put("a", Arrays.asList("a1","a2","a3"));
lm1.put("b", Arrays.asList("b1","b2","b3"));
lm1.put("c", Arrays.asList("c1","c2","c3"));
Map<String, List<String>> lm2 = new HashMap<>();
lm2.put("a", Arrays.asList("a3","a4","a5"));
lm2.put("e", Arrays.asList("e1","e2","e3"));
lm2.put("f", Arrays.asList("f1","f2","f3"));

//Function to merge lists
//If you make it a method, you can refer to the method
BinaryOperator<List<String>> mergeList = (left, right) -> Stream.of(left, right).flatMap(List::stream).collect(Collectors.toList());

//If this mergeList is used only by Collector, the following implementation will be faster and save memory.
// BinaryOperator<List<String>> mergeList = (l, r) -> {l.addAll(r); return l;};

// toMap()Merge with
Map<String, List<String>> lm3 =
    Stream.of(lm1, lm2)
          .flatMap(m -> m.entrySet().stream())
          .collect(Collectors.toMap(Entry::getKey, Entry::getValue, mergeList));

// Stream<List<E>>List<E>Collector to merge into
// Stream<E>List<E>Collectors to aggregate in#toList()Different from
Collector<List<String>, ?, List<String>> collector = Collector.of(ArrayList::new, List::addAll, mergeList, Characteristics.IDENTITY_FINISH);

// groupingBy()Merge with
Map<String, List<String>> lm4 =
    Stream.of(lm1, lm2)
          .flatMap(m -> m.entrySet().stream())
          .collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, collector)));

Generalization

Finally, I will give an example of a generalized version of the above and summarized in a utility class.

import static java.util.stream.Collectors.*;

import java.util.*;
import java.util.Map.Entry;
import java.util.function.*;
import java.util.stream.*;

public class MapMerger {

    // Map<K, V>Entry an array of<K, V>Expand to stream
    @SafeVarargs
    private static <K, V> Stream<Entry<K, V>> flatten(Map<K, V>... maps){
        return Arrays.stream(maps).flatMap(map -> map.entrySet().stream());
    }
    
    //Merge maps into different types of maps
    @SafeVarargs
    public static <K, V, R> Map<K, R> merge(Collector<V, ?, R> collector, Map<K, V>... maps){
        return flatten(maps).collect(groupingBy(Entry::getKey, mapping(Entry::getValue, collector)));
    }

    //Merge maps into maps of the same type
    @SafeVarargs
    public static <K, V> Map<K, V> merge(BiFunction<? super V, ? super V, ? extends V> mergeFunction, Map<K, V>... maps) {
        return flatten(maps).collect(toMap(Entry::getKey, Entry::getValue, mergeFunction::apply));
        //If you want to take advantage of the collector version implementation
//        Function<List<V>, V> finisher = values -> values.stream().reduce(mergeFunction::apply).get();
//        Collector<V, ?, V> collector = Collector.of(ArrayList::new, List::add, MapMerger::mergeIntoList, finisher, Characteristics.IDENTITY_FINISH);
//        return merge(collector, maps);
    }

    //Merge collection into list
    @SafeVarargs
    private static <E> List<E> mergeIntoList(Collection<E>... collections){
        return Arrays.stream(collections).flatMap(Collection::stream).collect(toList());
    }
    
    // Map<K, List<V>>Merge
    @SafeVarargs
    public static <K, V> Map<K, List<V>> merge(Map<K, List<V>>... maps){
        return merge(MapMerger::mergeIntoList, maps);
    }
    
    //Example of use
    public static void main(String[] args) {
        //Sample data 1
        Map<String, Integer> m1 = new HashMap<>();
        m1.put("a", 1);
        m1.put("b", 2);
        m1.put("c", 3);
        Map<String, Integer> m2 = new HashMap<>();
        m2.put("a", 100);
        m2.put("b", 200);
        m2.put("c", 300);
        
        //sum
        Map<String, Integer> m3 = merge(Integer::sum, m1, m2);
        Map<String, Integer> m4 = merge(summingInt(Integer::intValue), m1, m2);
        //average
        Map<String, Double> m5 = merge(averagingInt(Integer::intValue), m1, m2);
        
        //Sample data 2
        Map<String, List<String>> lm1 = new HashMap<>();
        lm1.put("a", Arrays.asList("a1","a2","a3"));
        lm1.put("b", Arrays.asList("b1","b2","b3"));
        lm1.put("c", Arrays.asList("c1","c2","c3"));
        Map<String, List<String>> lm2 = new HashMap<>();
        lm2.put("a", Arrays.asList("a3","a4","a5"));
        lm2.put("e", Arrays.asList("e1","e2","e3"));
        lm2.put("f", Arrays.asList("f1","f2","f3"));
        
        //Isomorphic merge
        Map<String, List<String>> lm3 = merge(lm1, lm2);
        Map<String, List<String>> lm4 = merge(MapMerger::mergeIntoList, lm1, lm2);
        
        // Map<String, String>Merge into
        Collector<List<String>, ?, String> collector = Collector.<List<String>, List<String>, String>of(
                ArrayList::new,
                List::addAll,
                MapMerger::mergeIntoList,
                List::toString);
        Map<String, String> lm5 = merge(collector, lm1, lm2);
        
        //output
        Stream.of(m3, m4, m5).forEach(System.out::println);
        Stream.of(lm3, lm4, lm5).forEach(System.out::println);
    }
}

Recommended Posts

[Java 8+] Merge maps
Merge Java Word documents
Study java arrays, lists, maps
Java
Java
Java learning (0)
Studying Java ―― 3
Java protected
[Java] Annotation
[Java] Module
Java array
Studying Java ―― 9
Java scratch scratch
Java tips, tips
Java methods
Java method
java (constructor)
Java array
[Java] ArrayDeque
java (override)
Java Day 2018
Java string
java (array)
Java static
Java serialization
java beginner 4
JAVA paid
Studying Java ―― 4
Java (set)
java shellsort
merge method
[Java] compareTo
Studying Java -5
java (interface)
Java memorandum
☾ Java / Collection
Java array
Studying Java ―― 1
[Java] Array
[Java] Polymorphism
Studying Java # 0
java framework
Java features
[Java] Inheritance
FastScanner Java
Java features
java beginner 3
Java memo
java (encapsulation)
Java inheritance
Java basics
Decompile Java
[Java] Annotation
java notes
java beginner
Java (add2)
JAVA (Map)
[java] interface
Java9 collection
Java methods
Java diary