List with inner List to Map Java Stream Api - java

I have a list of elements:
List<OuterElement> outerElements;
OuterElement object has 2 property:
String key;
List<InnerElement> innerElements;
InnerElement object has 1 property:
String value;
I need to create a map where for each innerElement will be created an Entry, where key will be outerElement key, and value will be innerElement value;
Map<String, String> // actually Map<OuterElement.key, InnerElement.value>
How to do it using Stream API?
I have tried something like this, but it doesn't work:
Map<String, String> result = outerElements.stream()
.forEach(outerElement -> outerElement.getInnerElements().stream().collect(Collectors.toMap(OuterElement::getKey, InnerElement::getValue));

In addition to Ruslan answer for java 8 you could use this one:
Map<String, List<String>> collect = elements.stream()
.collect(Collectors.groupingBy(OuterElement::getKey,
Collectors.collectingAndThen(Collectors.toList(), outers -> outers.stream()
.flatMap(o -> o.getInnerElements().stream())
.map(InnerElement::getValue)
.collect(Collectors.toList()))));
if you could use something like MultiValueMap:
MultiValueMap<String, String> map = elements.stream()
.collect(Collectors.groupingBy(OuterElement::getKey,
LinkedMultiValueMap::new,
Collectors.collectingAndThen(Collectors.toList(), outers -> outers.stream()
.flatMap(o -> o.getInnerElements().stream())
.map(InnerElement::getValue)
.collect(Collectors.toList()))));

Related

Mapping map values in Java using streams

I have a map, Map<String, Map<String, String>> myMap = new HashMap<>(); that I would like to remap to get it's values, so that I get as a result Map<String, String>.
Is it possible to do the mapping using stream API?
I have solved the problem using a for loop but I'm interested if that could be done using streams.
My solution:
Map<String, String> result = new HashMap<>();
myMap.forEach((k, v) -> {
result.putAll(v);
});
What I want is to get all the values from myMap and put them in a new Map.
If you are certain there are no duplicate keys, you can do it like this.
Map<String, String> res = myMap.values()
.stream()
.flatMap(value -> value.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);
If there may be duplicate keys between the inner maps, you will have to introduce merge function to resolve conflicts. Simple resolution keeping the value of the second encountered entry may look like this:
Map<String, String> res = myMap.values()
.stream()
.flatMap(value -> value.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));
Basically, stream the values, which are Maps, flatten them to a stream of entries and collect the entries in a new Map.
You need to flatten the entries of the nested maps which can be done using either flatMap() or mapMulty().
And then apply collect() with the minimalistic two-args flavor of Collector toMap() passed as an argument. It would be sufficient since you don't expect duplicates.
Here's an example using flatMap():
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> res = myMap.entrySet().stream() // stream of maps
.flatMap(entry -> entry.getValue().entrySet().stream()) // stream of map entries
.collect(Collectors.toMap(
Map.Entry::getKey, // key mapper
Map.Entry::getValue // value mapper
));
Example with Java 16 mapMulti() used for flattening the data:
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> res = myMap.entrySet().stream() // stream of maps
.<Map.Entry<String, String>>mapMulti((entry, consumer) ->
entry.getValue().entrySet().forEach(consumer) // stream of map entries
)
.collect(Collectors.toMap(
Map.Entry::getKey, // key mapper
Map.Entry::getValue // value mapper
));

map of map get outer map based on inner map value

How do I write below code using Java8?
for (Entry<Integer, Map<String, Object>> entry : data.entrySet()) {
Map<String, Object> value = entry.getValue();
if (value.get(Constants.USER_TRAN_ID).equals(stsTxn.getSeedTrade().getTransactionId())) {
closedTaxLotByTxnId = value;
break;
}
}
I am clueless after this
data.values().stream().map(e -> e.get(Constants.USER_TRAN_ID)).filter(txnId -> txnId.equals(stsTxn.getSeedTrade().getTransactionId()));
You don't need map. Just use filter with your criteria, and findFirst as terminal operation:
Optional<Map<String, Object>>
value = data.values()
.stream()
.filter(m -> m.get(Constants.USER_TRAN_ID).equals(stsTxn.getSeedTrade().getTransactionId()))
.findFirst();
If you want a default value (such as null) when no match is found, use:
Map<String, Object> closedTaxLotByTxnId =
data.values()
.stream()
.filter(m -> m.get(Constants.USER_TRAN_ID).equals(stsTxn.getSeedTrade().getTransactionId()))
.findFirst()
.orElse(null);

Could you help me to merge values of several maps?

I'm trying to do the following modification:
final Map<String, List<Map<String, String>>> scopes = scopeService.fetchAndCacheScopesDetails();
final Map<String, Map<String, String>> scopesResponse = scopes.entrySet().stream().collect
(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
.stream().collect(Collectors.toMap(s -> (String) s.get(SCOPE_NM), s -> (String) s.get(SCOPE_ID))))
);
But I face "Duplicate key" error, so I'd like to change scopeResponses to Map<String, Map<String, List<String>>>
Could you tell me how to merge values s -> (String) s.get(SCOPE_ID) into a List or Set in this situation?
You need to create a Set for the value of the inner Map, and supply a merge function:
final Map<String, Map<String, Set<String>>> scopesResponse = scopes.entrySet().stream().collect
(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
.stream().collect(Collectors.toMap(s -> s.get(SCOPE_NM),
s -> {Set<String> set= new HashSet<>(); set.add(s.get(SCOPE_ID)); return set;},
(s1,s2)->{s1.addAll(s2);return s1;}))));
Or, you can construct the inner Map with groupingBy:
final Map<String, Map<String, Set<String>>> scopesResponse2 = scopes.entrySet().stream().collect
(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
.stream().collect(Collectors.groupingBy(s -> s.get(SCOPE_NM),
Collectors.mapping(s -> s.get(SCOPE_ID),Collectors.toSet())))));
You can also do it using Guava's ListMultimap (multimap is like a map of lists):
Map<String, ListMultimap<String, String>> scopesResponse = scopes.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> toMultimap(e)));
where
static ImmutableListMultimap<String, String> toMultimap(
Map.Entry<String, List<Map<String, String>>> entry) {
return entry.getValue().stream().collect(ImmutableListMultimap.toImmutableListMultimap(
s -> (String) s.get(SCOPE_NM),
s -> (String) s.get(SCOPE_ID)
));
}
If the values in the lists turn out to be duplicated, and you don't want that, use SetMultimap instead.

How do I transform a Map<K,V1> to another Map<K,V2> using a map of functions in Java 8?

I want to transform a Map<String, Integer> to another Map<String, Long> using a map of functions in Java 8, matching the data with the functions by key in both maps. You can assume that both maps have the same keys.
I tried the following approach:
Map<String, Integer> inputData = new HashMap<>();
inputData.put("A",8);
inputData.put("B",7);
inputData.put("C",6);
Map<String, Function<Integer, Long>> transformers = new HashMap<>();
transformers.put("A", x -> x*2L);
transformers.put("B", x -> x+3L);
transformers.put("C", x -> x+11L);
Map<String, Long> mappedData = inputData.entrySet().stream()
.map(entry -> new AbstractMap.SimpleEntry<>(
entry.getKey(),
transformers.get(entry.getKey()).apply(entry.getValue())))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
Expected result: {A=16, B=10, C=17}.
Is there any simpler way of expressing "apply map of transformers to the map of inputData matching by key" in Java Streams API?
You may transform directly in the collector:
Map<String, Long> mappedData = inputData.entrySet().stream()
.collect(toMap(Map.Entry::getKey,
entry -> transformers.get(entry.getKey()).apply(entry.getValue())));
Such solution is shorter and does not create intermediate objects.
Were your inputData had the same value type (Map<String, Long>), you could perform the transformation in-place:
inputData.replaceAll((key, value) -> transformers.get(key).apply(value));
You can also start from transformers map itself, which looks slightly easier:
Map<String, Long> collect = transformers.entrySet().stream()
.collect(toMap(Map.Entry::getKey,
e -> e.getValue().apply(inputData.get(e.getKey()))));
If one extra line does not bother you, try
Map<String, Long> result = new HashMap<>(); // this line
inputData.forEach((k, v) -> result.put(k, transformers.get(k).apply(v));

java 8 streams unwind or emit lists to get single values

I have a map like so:
Map<String, List<String>> map
Now I want do take all the list entries from the map values and use them as key in another map.
Map<String, String> newMap = map.entrySet()
.stream()
.fiter(somefilter -> true)
.collect(Collectors.toMap(
k -> k.getValue(), // I want to have every single value as key
v -> v.getKey());
Is there a way to "unwind" arrays in Java 8 streams? In MongoDB I would write something like this:
Map<String, String> newMap = map.entrySet()
.stream()
.fiter(somefilter -> true)
.unwind(entry -> entry.getValue())
.collect(Collectors.toMap(
k -> k.getValue(),
v -> v.getKey());
One way would be to create streams of pairs of value/key (for example with an array) and flat map them for collection:
Map<String, String> newMap = map.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(s -> new String[] { s, e.getKey() }))
.collect(toMap(array -> array[0], array -> array[1]));
The String array looks a bit hacky though...

Categories

Resources