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)
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 ().
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;
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)));
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)));
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