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);
}
Related
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));
Say I have a HashMap and I want to insert the same value to a list of keys. How can I do this with Java 8 without iterating through all the keys and inserting the value? This is more of a Java Streams question.
Here is the straight forward way of doing it. This is a sample code that I wrote to demonstrate what I wanted to achieve.
public void foo(List<String> keys, Integer value) {
Map<String, Integer> myMap = new HashMap<>();
for (String key : keys) {
myMap.put(key, value);
}
}
Is there a simpler way of doing the above using Java 8 streams? How can I avoid the for loop using Java 8 streams. Thanks!
[Edit-1] A better code snippet below.
public void foo() {
Map<String, Integer> myMap = new HashMap<>();
List<String> keys = getKeysFromAnotherFunction();
Integer value = getValueToBeInserted(); // Difficult to show my actual use case. Imagine that some value is getting computed which has to be inserted for the keys.
for (String key : keys) {
myMap.put(key, value);
}
List<String> keys2 = getNextSetOfKeys();
Integer newValue = getValueToBeInserted();
for (String key : keys2) {
myMap.put(key, newValue);
}
}
Using collector, something like:
Map<String, Integer> myMap = keys.stream()
.collect(Collectors.toMap(key -> key,
val -> value, (a, b) -> b));
I think that your question is about factoring out some piece of code more than converting traditional for loops into stream constructs.
Suppose you have the following generic utility method:
public static <K, V, M extends Map<K, V>> M fillMap(
Supplier<List<K>> keysFactory,
Supplier<V> singleValueFactory,
Supplier<M> mapFactory) {
M map = mapFactory.get();
List<K> keys = keysFactory.get();
V singleValue = singleValueFactory.get();
keys.forEach(k -> map.put(k, singleValue));
return map;
}
Then, you could use the above method as follows:
Map<String, Integer> myMap = fillMap(() -> getKeysFromAnotherFunction(),
() -> getValueToBeInserted(),
HashMap::new); // create HashMap
myMap = fillMap(() -> getNextSetOfKeys(),
() -> getValueToBeInserted(),
() -> myMap); // use previously created map
There are variants for the code above, i.e., the method could receive a Map<K, V> instance instead of a Supplier<Map<K, V>>, or it might even be overloaded to support both variants.
I have the following code:
private Map<String, Object> flatten(Map<String, Object> source) {
Map<String, Object> result = new LinkedHashMap<>();
source.forEach((k, v) -> {
if (v instanceof Map) {
Map<String, Object> subMap = flatten((Map<String, Object>) v);
for (String subkey : subMap.keySet()) {
result.put(k + "." + subkey, subMap.get(subkey));
}
} else result.put(k, v);
});
return result;
}
The above code flattens a given Map
Ex:
{
"hello": {
"hi": {
"hola": 1
}
}
}
to
{
"hello.hi.hola": 1
}
I would like to use Java8 streams and implement the same logic, How can I do that?
I was going to post something similar to #John Bollinger's answer. Instead I'll offer a somewhat cleaner version using StreamEx:
static Map<String, Object> flatten(Map<?, ?> map) {
return flatten("", map)
.mapKeys(k -> k.substring(1))
.toMap();
}
static EntryStream<String, Object> flatten(String key, Object value) {
if (value instanceof Map) {
return EntryStream.of((Map<?, ?>)value)
.flatMapKeyValue((k, v) -> flatten(key + "." + k, v))
.chain(EntryStream::of);
} else {
return EntryStream.of(key, value);
}
}
Nope, the Stream API has not been designed to go with the recursion well. There are not much possibilities to "enhance" your solution using Stream API nor refactor. From the definition of Stream API from its package info is obvious that their usage has to be non-interfering, stateless and without side-effects - thus I can't imagine how you would achieve this using this API.
I find the only possible usage amending the inner block of the if-condition which brings no real benefit:
if (v instanceof Map) {
Map<String, Object> subMap = flatten((Map<String, Object>) v);
subMap.keySet().stream().forEach(subkey -> result.put(k + "." + subkey, subMap.get(subkey)));
} else result.put(k, v);
The task you present is not well suited to implementation via streams. What you already have is concise and clean. Recursion is a particular problem because lambdas cannot refer to themselves or directly recurse.
However, you don't have to express stream operations in terms of lambdas. You can express them in terms of instances of ordinary classes, too, and such instances can recurse. Thus, you might rewrite your flatten() method like so:
private Map<String, Object> flatten(Map<String, Object> source) {
Function<Entry<?, ?>, Stream<Entry<String, Object>>> flattener
= new Function<Entry<?, ?>, Stream<Entry<String, Object>>>() {
public Stream<Entry<String, Object>> apply(Entry<?, ?> e) {
String k = e.getKey().toString();
Object v = e.getValue();
if (v instanceof Map) {
return ((Map<?, ?>) v).entrySet()
.stream()
.flatMap(this)
.map(n -> new SimpleEntry<>(k + "." + n.getKey(), n.getValue()));
} else {
return Stream.of(new SimpleEntry<>(k, v));
}
}
};
return source.entrySet()
.stream()
.flatMap(flattener)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
The main body of that, at the end, is a pretty simple stream pipeline. The magic happens in the flatMap() step, with the provided flattening Function. It, too, is written in terms of stream operations, and it recurses by passing itself to its own invocation of Stream.flatMap().
But I cannot imagine why anyone would prefer this mess to the code you started with.
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();
}
I have the following setup:
Map<Instant, String> items;
...
String renderTags(String text) {
// Renders markup tags in a string to human readable form
}
...
<?> getItems() {
// Here is where I need help
}
My problems is, the strings that are the values of the items map are marked up with tags. I want getItems() to return all the items, but with the strings parsed using the renderTags(String) method. Something like:
// Doesn't work
items.entrySet().stream().map(e -> e.setValue(renderTags(e.getValue())));
What is the most effective way to do this?
If you want a Map as result:
Map<Instant, String> getItems() {
return items.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> renderTags(e.getValue())));
}
If you want to modify an existing map instead of generating the new one (as in your example), there's no need to use the stream at all. Use Map.replaceAll:
items.replaceAll((k, v) -> renderTags(v));
return items;
If you want to keep the original map unchanged, consult other answers.
You can try it this way with Collectors.toMap():
Map<Instant, String> getItems() {
return items.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> renderTags(entry.getValue())
));
}
By the way, if the name says simply "get", you shouldn't generally transform it in there. One expects a getter to be simple and not costy at all.
Since Java 9 you can also do:
Entry<String, String> entry = Map.entry("a", "b");
In your Map it would be used like this:
Map<Instant, String> getItems() {
return items.entrySet()
.stream()
.map(entry -> Map.entry(entry.getKey(), renderTags(entry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);
}
An alternative could be
Map<Instant, String> getItems() {
return items.entrySet().stream()
.peek(entry -> entry.setValue(renderTags(entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey,e -> e.getValue()));
}
see Baeldung