Find a map in list of map java stream - java

I am iterating through a List of Hashmap to find the required HashMap object using the following code.
public static Map<String, String> extractMap(List<Map<String, String>> mapList, String currentIp) {
for (Map<String, String> asd : mapList) {
if (asd.get("ip").equals(currentIp)) {
return asd;
}
}
return null;
}
I was thinking about using Java 8 stream. This is the code I used to display the required object.
public static void displayRequiredMapFromList(List<Map<String, String>> mapList, String currentIp) {
mapList.stream().filter(e -> e.get("ip").equals(currentIp)).forEach(System.out::println);
}
I couldn't get the required Map from the stream using following code
public static Map<String, String> extractMapByStream(List<Map<String, String>> mapList, String currentIp) {
return mapList.stream().filter(e -> e.get("ip").equals(currentIp))
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
}
This causes syntax error Type mismatch: cannot convert from Map to Map. What do I have to put here to get Map?

You don't want to .collect anything. You want to find the first map that matches the predicate.
So you should use .findFirst() instead of .collect().
toMap() is for building a Map from the elements in the stream.
But you don't want to do that, each element is already a Map.

This will will work, the other examples without orElse() don't compile (at least they don't in my IDE).
mapList.stream()
.filter(asd -> asd.get("ip").equals(currentIp))
.findFirst()
.orElse(null);
The only thing I would add as a suggestion is to return Collections.emptyMap(), this will save a null check in the calling code.
To get the code to compile without orElse you need to change the method signature to:
public static Optional<Map<String, String>> extractMap(List<Map<String, String>> mapList, String currentIp)

User this
public static Map<String, String> extractMapByStream(List<Map<String, String>> mapList, String currentIp) {
return mapList.stream().filter(e -> e.get("ip").equals(currentIp))
.findFirst().get();
}

Related

Java8 Stream grouping by with toMap issue

I have a class named ConfigKey
public class ConfigKey {
String code;
String key;
String value;
//omit setter and getter
}
I want to convert List<ConfigKey> to Map<String, Map<String, Object>>, here is my method definition
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
however I want to do some changes, for each ConfigKey put another key to the map, e.g.
{ "code": "code1","key", "key1", "value": "value1"}
to Map
{"code1": {"key1":"value1", "prefix_key1": "value1" }
is there any API to do it like bellow:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap("prefix_" + ConfigKey::getKey, ConfigKey::getValue))
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
You can make use of the Collector.of() factory method, which allows you to create your own collector:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode, Collector.of(
HashMap::new, (m, c) -> {
m.put(c.getKey(), c.getValue());
m.put("prefix_" + c.getKey(), c.getValue());
}, (a, b) -> {
a.putAll(b);
return b;
}
)));
}
But honestly that seems a bit messy, and maybe a normal loop would've been better. The streams intention was to provide an api which does things in a more readable manner, but when you have to hackaround that construct, by introducing some extremely unreadable logic then it is almost always the better option to just do it the old way:
public Map<String, Map<String, Object> convert (List<ConfigKey> list) {
Map<String, Map<String, Object>> map = new HashMap<>();
for (ConfigKey ck : list) {
Map<String, Object> inner = map.computeIfAbsent(ck.getCode(), k -> new HashMap<>());
inner.put(ck.getKey(), ck.getValue());
inner.put("prefix_" + ck.getKey(), ck.getValue());
}
return map;
}
You can first add the new entries to the map and then group them:
private Map<String, Map<String, Object>> convert(List<ConfigKey> list) {
new ArrayList<>(list).stream().map(configKey -> new ConfigKey(configKey.getCode(), "prefix_" + configKey.getKey(), configKey.getValue())).forEachOrdered(list::add);
return list.stream().collect(Collectors.groupingBy(ConfigKey::getCode,
Collectors.toMap(ConfigKey::getKey, ConfigKey::getValue)));
}
I cloned the list (in order to prevent ConcurrentModificationException), then changed the keys to the "new" ones (with map) and added them to the original list - forEachOrdered(list::add).
Because the 'code' field was not changed, both entries will use it which results in 2 entries in the map

Get HashMapsValues from nested HashMap<String, HashMap<String, String>> filtered against a List<String>

I have a nested String HashMap and a List of object. The object has a String property to be matched against the values of the inner HashMap.
I'm trying to find a single liner using stream() and Collectors for the below java code
HashMap<String, HashMap<String, String>>PartDetailsHMap=new HashMap<String, HashMap<String, String>>()
List<Part> partList=new ArrayList<Part>();
for(int i=0;i<partList.size();i++)
String partId = partList.get(i).getPropertyValue("part_id");
for(HashMap< String, String> PartPropsHMap:PartDetailsHMap.values())
{
if(PartPropsHMap.containsValue(itemId))
{
collectingPartMap.put(partList.get(i), PartPropsHMap);
break;
}
}
}
If needed I can extract String property in a List<String>.
Looking for a one liner using stream().
Something like this should work:
Map<String, Map<String, String>> PartDetailsHMap = new HashMap<>();
List<Part> partList = new ArrayList<>();
Map<Part, Map<String, String>> collectingPartMap = partList.stream()
.map(part -> PartDetailsHMap.values()
.stream()
.filter(partPropsHMap -> partPropsHMap.containsValue(part.getPropertyValue("part_id")))
.findFirst()
.map(partPropsHMap -> new SimpleEntry<Part, Map>(part, partPropsHMap))
.get()
)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
I've used SimpleEntry class in AbstractMap to carry the context of Part along with the map that we've found to the next operation - collect.
Caveat: I feel if the option without streams is cleaner and does the job, I would go with that. Given that the manipulation you need here is fairly involved, it would benefit in the long run to keep it readable, than something clever.
An alternate approach slightly improving the current answer could be to not perform a get without an isPresent check. This could be achieved by using filter and map
Map<Part, Map<String, String>> collectingPartMap = partList.stream()
.map(part -> partDetailsHMap.values().stream()
.filter(innerMap -> innerMap.containsValue(part.getPartId())) // notice 'getPartId' for the access
.findFirst()
.map(firstInnerMap -> new AbstractMap.SimpleEntry<>(part, firstInnerMap)))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Set of String with Stream to HashMap in Java 8

How do I create HashMap of String and List of String out of Set of String with Stream?
Set<String> mySet;
Map<String, List<String>> = mySet.stream().map(string -> {
// string will be my key
// I have here codes that return List<String>
// what to return here?
}).collect(Collectors.toMap(.....)); // what codes needed here?
Thank you.
You don't need the map() step. The logic that produces a List<String> from a String should be passed to Collectors.toMap():
Map<String, List<String>> map =
mySet.stream()
.collect(Collectors.toMap(Function.identity(),
string -> {
// put logic that returns List<String> here
}));
The map operation is useless here, because you don't want to change the string itself, or you would have to map it to an Entry<String, List<String>> and then collect them, but this is not easier.
Instead just build the map, the string as key and get your codes as values :
Map<String, List<String>> map =
mySet.stream().collect(Collectors.toMap(str->str, str-> getCodesFromStr(str));
If you want to know, how it would be with a map operation and use Entry (a pair) :
Map<String, List<String>> = mySet.stream().map(str->
new AbstractMap.SimpleEntry<String,List<String>>(str, getCodesFromStr(str))
).collect(Collectors.toMap(Entry::getKey, Entry::getValue));

Flattening a nested Hashmap using Stream

I have a nested HashMap<String,Object> and I want to create a HashMap<String,String> by flattening the Hashmap. I have tried the solution from Recursively Flatten values of nested maps in Java 8. But I am unable to use the class FlatMap as mentioned in the answer.
I have also tried the solution in the question itself, still I am missing something. Then I found a similar use case and came up with the following solution. But it seems like I am missing something as a parameter for the lambda function flatMap .
public static void main(String[] args) {
Map<String,Object> stringObjectMap= new HashMap<String,Object>();
stringObjectMap.put("key1","value1");
stringObjectMap.put("key2","value2");
Map<String,Object> innerStringObjectMap = new HashMap<>();
innerStringObjectMap.put("i1key3","value3");
innerStringObjectMap.put("i1key4","value4");
innerStringObjectMap.put("i1key5","value5");
stringObjectMap.put("map1",innerStringObjectMap);
Map<String,Object> innerStringObjectMap2 = new HashMap<>();
innerStringObjectMap.put("i2key6","value6");
innerStringObjectMap2.put("i2key7","value7");
innerStringObjectMap.put("i1map2",innerStringObjectMap2);
Map<String,Object> collect =
stringObjectMap.entrySet().stream()
.map(x -> x.getValue())
.flatMap(x -> x) //I aint sure what should be give here
.distinct(); //there was a collect as List which i removed.
//collect.forEach(x -> System.out.println(x));
}
What is a better solution for flattening a nested map? I am not just interested in the values, but also the keys in the map. That is the reason why I decided to flatten the map to get another map (I am not sure if this is even possible)
EDIT - Expected Output
key1 - value1
key2-value2
map1 ="" //this is something i will get later for my purpose
i1key3=value3
.
.
i1map2=""
.
.
i2key7=value7
I modified the class from the mentioned answer according to your needs:
public class FlatMap {
public static Stream<Map.Entry<?, ?>> flatten(Map.Entry<?, ?> e) {
if (e.getValue() instanceof Map<?, ?>) {
return Stream.concat(Stream.of(new AbstractMap.SimpleEntry<>(e.getKey(), "")),
((Map<?, ?>) e.getValue()).entrySet().stream().flatMap(FlatMap::flatten));
}
return Stream.of(e);
}
}
Usage:
Map<?, ?> collect = stringObjectMap.entrySet()
.stream()
.flatMap(FlatMap::flatten)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(u, v) -> throw new IllegalStateException(String.format("Duplicate key %s", u)),
LinkedHashMap::new));
Attention:
Be sure to use the provided collect with a LinkedHashMap, otherwise the order will be screwed up.
I have used the function from https://stackoverflow.com/a/48578105/5243291. But I used the function in a different way.
Map<Object, Object> collect = new HashMap<>();
stringObjectMap.entrySet()
.stream()
.flatMap(FlatMap::flatten).forEach(it -> {
collect.put(it.getKey(), it.getValue());
});
Function again
public static Stream<Map.Entry<?, ?>> flatten(Map.Entry<?, ?> e) {
if (e.getValue() instanceof Map<?, ?>) {
return Stream.concat(Stream.of(new AbstractMap.SimpleEntry<>(e.getKey(), "")),
((Map<?, ?>) e.getValue()).entrySet().stream().flatMap(FlatMap::flatten));
}
return Stream.of(e);
}

Java 8 extract first key from matching value in a Map

Suppose I have a map of given name, surname pairs and I want to find the given name of the first entry in that map that has the surname matching a certain value.
How would we do this in a java 8 fashion.
In my test case example below I put two ways that would do it.
However the first one (looking for the given name of the first person with a surname of "Donkey") will throw java.util.NoSuchElementException: No value present so it is not safe.
The second one works but it is not only harder to read but it it is a bit not quite functional.
Just wondering if someone here would suggest me an easier clearer way of achieving this using either stream() or forEach() or both.
#Test
public void shouldBeAbleToReturnTheKeyOfTheFirstMatchingValue() throws Exception {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
String keyOfTheFirst = names.entrySet().stream().filter(e -> e.getValue().equals("Doe")).findFirst().get().getKey();
assertEquals("John", keyOfTheFirst);
try {
names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst().get();
} catch (NoSuchElementException e){
// Expected
}
Optional<Map.Entry<String, String>> optionalEntry = names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst();
keyOfTheFirst = optionalEntry.isPresent() ? optionalEntry.get().getKey() : null;
assertNull(keyOfTheFirst);
}
Thank you in advance.
To return a default value if there is no match, use Optional#orElse
names.entrySet().stream()
.filter(e -> e.getValue().equals("Donkey"))
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
From a similar question:
public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
return map.entrySet()
.stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
Then you can select the first, if you want to. Remember that the key is unique, the value is not.
Edit:
The whole code (thanks #Peter Lawrey)
package test;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
Optional<String> firstKey = names.entrySet().stream()
.filter(entry -> Objects.equals(entry.getValue(), "Doe"))
.map(Map.Entry::getKey).findFirst();
if (firstKey.isPresent()) {
System.out.println(firstKey.get());
}
}
}
The solution provided by #Misha is the best one if you don't want to use the third-party code. My library has the special shortcut method ofKeys for such cases as I discovered that it's quite common task:
StreamEx.ofKeys(names, "Donkey"::equals).findFirst().orElse(null);
I like old fashioned:
static <K, V> K findFirstKeyByValue(Map<K, V> map, String value) {
for (Entry<K, V> e : map.entrySet())
if (e.getValue().equals(value))
return e.getKey();
return null;
}
Below is my code snippet to get key from map,
Map<String,String> pageDetails = new HashMap<String,String>();
public String getAssociatedKey(){
pageDetails.entrySet().stream().filter( e -> e.getValue().contains("John").findFirst().get().getKey();
}
In order to avoid null pointer exception if map entry exist with null value:
private String getValueByKey(Map<String, String> map, String key)
{
return map.entrySet().stream().filter(e ->
StringUtils.equalsIgnoreCase(e.getKey(), key)).findFirst().get()
.getValue();
}

Categories