java 8 map.get multiple key values - java

I have following code where I want to get value for multiple keys which starts with same name:
for example contents_of_a1, contents_of_ab2, contents_of_abc3
Optional.ofNullable(((Map<?, ?>) fieldValue))
.filter(Objects::nonNull)
.map(coverages -> coverages.get("contents_of_%"))
.filter(Objects::nonNull)
.filter(LinkedHashMap.class::isInstance)
.map(LinkedHashMap.class::cast)
.map(contents -> contents.get("limit"))
.map(limit -> new BigDecimal(String.valueOf(limit)))
.orElse(new BigDecimal(number));
How can I pass contents_of%

I don't know the reasons behind the data structure and what you want to achieve.
However, it is not important as this can be easily reproduced.
Using of Optional is a good start, however, for iterating and processing multiple inputs, you need to use java-stream instead and then Optional inside of collecting (I assume you want Map<String, BigDecimal output, but it can be adjusted easily).
Also, note .filter(Objects::nonNull) is meaningless as Optional handles null internally and is never passed to the next method.
final Map<String, Map<?, ?>> fieldValue = Map.of(
"contents_of_a", new LinkedHashMap<>(Map.of("limit", "10")),
"contents_of_b", new HashMap<>(Map.of("limit", "11")), // Different
"contents_of_c", new LinkedHashMap<>(Map.of("amount", "12")), // No amount
"contents_of_d", new LinkedHashMap<>(Map.of("limit", "13")));
final List<String> contents = List.of(
"contents_of_a",
"contents_of_b",
"contents_of_c",
// d is missing, e is requested instead
"contents_of_e");
final int number = -1;
final Map<String, BigDecimal> resultMap = contents.stream()
.collect(Collectors.toMap(
Function.identity(), // key
content -> Optional.of(fieldValue) // value
.map(coverages -> fieldValue.get(content))
.filter(LinkedHashMap.class::isInstance)
// casting here to LinkedHashMap is not required
// unless its specific methods are to be used
// but we only get a value using Map#get
.map(map -> map.get("limit"))
.map(String::valueOf)
.map(BigDecimal::new)
// prefer this over orElse as Optional#orElseGet
// does not create an object if not required
.orElseGet(() -> new BigDecimal(number))));
// check out the output below the code
resultMap.forEach((k, v) -> System.out.println(k + " -> " + v));
Only the content for a is used as the remaining were either not an instance of LinkedHashMap, didn't contain a key limit or were not among requested contents.
contents_of_a -> 10
contents_of_b -> -1
contents_of_e -> -1
contents_of_c -> -1

If you want to filter a map for which key starting with "contents_of_", you can do this for Java 8:
Map<String, Object> filteredFieldValue = fieldValue.entrySet().stream().filter(e -> {
String k = e.getKey();
return Stream.of("contents_of_").anyMatch(k::startsWith);
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Related

Filter operation via Stream and maintain as map instead of Stream

I am currently getting a map data from an external api call.
I want to ensure the data is not null or empty and perform a set of operations on it
by filtering to a specific key in the map and capturing results into another object.
The key itself is comma separated.
Example key / value in map.
"key1,key2,key3,id100" : {
"val1: "",
"val2: "",
"val3: "",
... others
}
I am filtering to capture all values under this key (so data cal1, val2, val3 and others)
and then perform some operations.
But when I perform the filter as shown, I end up with a stream.
Thus Instead of just a Map<String, Object>, I end up with Stream<Map.Entry<String, Object>>.
Tried flatmap and getting following error:
no instance(s) of type variable(s) U exist so that
Stream<Entry<String, Object>> conforms to Optional
How could I convert it back to a Map from the Stream or a better way to filter this? Thanks.
Could have just done this via a for loop without Streams but trying to see how
I could achieve this in a Stream implementation thus not looking for a for loop solution. Please advice. Thanks.
private NewObject get() {
Map<String, Object> data = // data filled in by an external rest call;
return Optional.ofNullable(data)
// using flatmap here throws above error
.map(Map::entrySet)
.map(entries -> entries.stream()
.filter(entry -> entry.getKey().contains("id100))
// I wish to carry on further operations from here by using the filtered map.
// having issues cos capturedData is a Stream
// even if using flatmap at this stage, capturedData is still a Stream.
// wanting to do the following but can't due to it being a Stream and not a map
).map(capturedData -> {
Map<String, Object> at = (Map<String, Object>) capturedData;
NewObject newObject = new NewObject();
newObject.setName((String) at.get("val1"));
return newObject;
}).orElse(null);
}
Use map to construct the NewObject and use findFirst to get the first value (as per your comment, there will be only one entry whose key has substring id100). Finally use flatMap to unwrap the Optional<NewObject>.
return Optional.ofNullable(data)
.map(Map::entrySet)
.flatMap(entries -> entries.stream()
.filter(entry -> entry.getKey().contains("id100"))
.map(entry -> {
NewObject newObject = new NewObject();
Map<String, String> nestedMap = (Map<String, String>) entry.getValue();
newObject.setName(nestedMap.get("val1"));
return newObject;
})
.findFirst())
.orElse(null);
This code below filters the entryset in data, collects it to a set before performing the next set of operations. findFirst is used so that there is only ever a single entry to deal with.
Optional.ofNullable(data)
.map(Map::entrySet)
.map(entries ->
entries
.stream()
.filter(e -> e.getKey().contains("id1000")).collect(Collectors.toSet()))
.stream()
.findFirst()
.map(capturedData -> {
Map<String, Object> map = (Map<String, Object>) capturedData;
NewObject newObject = new NewObject();
newObject.setName((String) at.get("val1"));
return newObject;
})
.orElse(null);

Construct a new map using List of Maps using Java 8 streams

I converted below json data (in example) to List<Map<String, String>> and from that i want to construct a new Map using Java 8 streams and the output should look like below. could someone help me with this?
Key value
Service1: DEACTIVATED
Service2: ACTIVATED
Service3: DEACTIVATED
Ex:
[
{
name=Service1,
desiredState=DEACTIVATED
},
{
name=Service2,
desiredState=ACTIVATED
},
{
name=Service3,
desiredState=DEACTIVATED
}
]
From what I could comprehend, you aim to convert List<Map<String,String>> to Map<String,String>.
List<Map<String,String>> myMap = .... // map which you have already.
Map<String,String> resultMap = myMap.stream()
.flatMap(map -> map.entrySet().stream()) // Get a flatMap of the entryset. This will form a stream of Map.Entry
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (k1, k2) -> k2));
Here (k1,k2) -> k2 is a merge function in case if there are multiple entries for same keys while constructing the resultMap.
If I get it well, you need to merge your maps in a way such that the value of the name key is the key that maps to the value of the desiredState key. You could do it this way:
Map<String, String> result = listOfMaps.stream()
.collect(Collectors.toMap(
m -> m.get("name"),
m -> m.get("desiredState"),
(o, n) -> n));
This assumes that all maps from the list have name and desiredState entries. The (o, n) -> n merge function must be provided, in case there are collisions when creating the result map (i.e. entries with the same key). Here, between old and new values, I've chosen the new value.

Java 8: change type of entries return Map

I got the follwoing question:
I have a Map<?,?> and I parse it from an PList file, simplified like:
Map<String, String> m = (Map<String, String>) getMap();
The getMap() method just reads the file (.plist).
I want to parse all the values to String, but unfortunately the Map contains Integers, causing an error later in the process.
So I wanted to write a method using the filter to convert everything to String:
My approach is:
m.entrySet().stream()
.map(e -> e.getValue())
.filter(e -> e instanceof Integer)
.map(e -> String.valueOf(e))
.collect(Collectors.toMap(e -> e.keys(), e -> e.getValue()));
The problem is, that the collect at the end is not working, how can I fix this?
The result should be a map again.
Thank you a lot!
You're misunderstanding how Collectors.toMap works - it takes two functions, one that given an entry produces a new key, and one that given an entry produce a new value. Each entry in the map then has both of these functions applied to it and the resulting key/value for that single element are used to construct the new entry in the new map.
Also, by mapping each entry to just the value, you lose the association between keys and values, which means you can't reconstruct the map correctly.
A corrected version would be:
Map<String, String> n;
n = m.entrySet()
.stream()
.filter(e -> e.getValue() instanceof Integer)
.collect(Collectors.toMap(e -> e.getKey(),
e -> String.valueOf(e.getValue())));
You have a few errors in your code.
First, as you are mapping each entry to its value, you are losing the keys.
Then, when you filter, you are keeping only Integer values in the stream, which will yield an incomplete map.
Finally, in Collectors.toMap you are using e.keys() and e.getValue, which are incorrect, because e.keys() is not a method of neither Map.Entry nor String, and because you'd need to use e.getValue() instead of e.getValue.
The code should be as follows:
m.entrySet().stream().collect(Collectors.toMap(
e -> e.getKey(),
e -> e.getValue() instanceof Integer ?
String.valueOf(e.getValue()) :
e.getValue()));
The replaceAll method is probably more suitable for this. It can be used like:
m.replaceAll((k, v) -> String.valueOf(v));
Map<String, String> m2 = m.entrySet().stream()
.collect(Collectors.toMap(e -> Objects.toString(e.getKey()),
e -> Objects.toString(e.getValue())));
This will turn both key and value into strings by toString. A null (value) will be turned into "null".

Processing HashMap using Java 8 Stream API

I have a hash table in the form
Map<String, Map<String,Double>
I need to process it and create another one having the same structure.
Following a sample to explain the goal
INPUT HASH TABLE
----------------------------
| | 12/7/2000 5.0 |
| id 1 | 13/7/2000 4.5 |
| | 14/7/2000 3.4 |
...
| id N | .... |
OUTPUT HASH TABLE
| id 1 | 1/1/1800 max(5,4.5,3.4) |
... ...
In particular, the output must have the same keys (id1, ..., id n)
The inner hash table must have a fixed key (1/1/1800) and a processed value.
My current (not working) code:
output = input.entrySet()
.stream()
.collect(
Collectors.toMap(entry -> entry.getKey(),
entry -> Collectors.toMap(
e -> "1/1/2000",
e -> {
// Get input array
List<Object> list = entry.getValue().values().stream()
.collect(Collectors.toList());
DescriptiveStatistics stats = new DescriptiveStatistics();
// Remove the NaN values from the input array
list.forEach(v -> {
if(!new Double((double)v).isNaN())
stats.addValue((double)v);
});
double value = stats.max();
return value;
}));
Where is the issue?
Thanks
The issue is trying to call Collectors.toMap a second type inside the first Collectors.toMap. Collectors.toMap should be passed to a method that accepts a Collector.
Here's one way to achieve what you want:
Map<String, Map<String,Double>>
output = input.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey(),
e -> Collections.singletonMap (
"1/1/1800",
e.getValue()
.values()
.stream()
.filter (d->!Double.isNaN (d))
.mapToDouble (Double::doubleValue)
.max()
.orElse(0.0))));
Note that there's no need for a second Collectors.toMap. The inner Maps of your output have a single entry each, so you can use Collections.singletonMap to create them.
Your original code can be solved using Collections.singletonMap instead of Collectors.toMap
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(
Collectors.toMap(entry -> entry.getKey(),
entry -> {
// Get input array
List<Object> list = entry.getValue().values().stream()
.collect(Collectors.toList());
DescriptiveStatistics stats = new DescriptiveStatistics();
// Remove the NaN values from the input array
list.forEach(v -> {
if(!new Double((double)v).isNaN())
stats.addValue((double)v);
});
double value = stats.max();
return Collections.singletonMap("1/1/2000", value);
}));
Or make the nested Collectors.toMap a part of an actual stream operation
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> Stream.of(entry.getValue()).collect(Collectors.toMap(
e -> "1/1/2000",
e -> {
// Get input array
List<Object> list = e.values().stream()
.collect(Collectors.toList());
DescriptiveStatistics stats = new DescriptiveStatistics();
// Remove the NaN values from the input array
list.forEach(v -> {
if(!new Double((double)v).isNaN())
stats.addValue((double)v);
});
double value = stats.max();
return value;
}))));
though that’s quiet a baroque solution.
That said, you should be aware that there’s the standard DoubleSummaryStatistics making DescriptiveStatistics unnecessary, though, both are unnecessary if you only want to get the max value.
Further, List<Object> list = e.values().stream().collect(Collectors.toList()); could be simplified to List<Object> list = new ArrayList<>(e.values()); if a List is truly required, but here, Collection<Double> list = e.values(); would be sufficient, and typing the collection with Double instead of Object makes the subsequent type casts unnecessary.
Using these improvements for the first variant, you’ll get
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(
Collectors.toMap(entry -> entry.getKey(),
entry -> {
Collection<Double> list = entry.getValue().values();
DoubleSummaryStatistics stats = new DoubleSummaryStatistics();
list.forEach(v -> {
if(!Double.isNaN(v)) stats.accept(v);
});
double value = stats.getMax();
return Collections.singletonMap("1/1/2000", value);
}));
But, as said, DoubleSummaryStatistics still is more than needed to get the maximum:
Map<String, Map<String,Double>> output = input.entrySet()
.stream()
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> {
double max = Double.NEGATIVE_INFINITY;
for(double d: entry.getValue().values())
if(d > max) max = d;
return Collections.singletonMap("1/1/2000", max);
}));
Note that double comparisons always evaluate to false if at least one value is NaN, so using the right operator, i.e. “value possibly NaN” > “current max never NaN”, we don’t need an extra conditional.
Now, you might replace the loop with a stream operation and you’ll end up at Eran’s solution. The choice is yours.

How to use double colon in Stream.collect for HashMap?

I want to read a csv file into a hashmap by using the first column as a key, the second column as a value, and ignoring the third column.
I wrote the following code and it works. I would like to know how to rewrite the syntax with double colon "::".
I check the API docs, but most of examples are using List instead of Map.
I used a string to mock a csv file: "A,1,!","B,2,#","C,3,#","D,4,$","E,5,%"
Map<String,String> maps = Stream.of("A,1,!","B,2,#","C,3,#","D,4,$","E,5,%")
.collect(() -> new HashMap<String,String>(),
(map, line) -> {String x[] = line.split(","); map.put(x[0],x[1]);},
(map1, map2) -> map1.putAll(map2));
System.out.println(maps);
Thanks,
Ian
Personally I would do this:
Map<String, String> maps = Stream.of("A,1,!", "B,2,#", "C,3,#", "D,4,$", "E,5,%").
map(line -> line.split(",")).
collect(HashMap::new, (map, line) -> map.put(line[0], line[1]), HashMap::putAll);
i.e. separate out the logic into distinct stream transformation operations. Doing the map in the collect clouds the intent of the code.
I do not think you want to be using the concrete collect() with the supplier, accumulator and combiner.
You should rely more on higher level methods, this becomes then:
Map<String, String> map = Stream.of("A,1,!","B,2,#","C,3,#","D,4,$","E,5,%")
.map(line -> line.split(","))
.collect(Collectors.toMap(
array -> array[0],
array -> array[1]
));
Which does the following:
Create a Stream<String>.
Map it to a Stream<String[]>.
Collect the results via a Collectors.toMap which takes a key mapper and a value mapper as arguments.
Here I map the array to array[0] for the key.
Here I map the array to array[1] for the value.
Then to confirm it works I print:
map.forEach((k, v) -> System.out.println("Key = " + k + " / Value = " + v));
Which gives:
Key = A / Value = 1
Key = B / Value = 2
Key = C / Value = 3
Key = D / Value = 4
Key = E / Value = 5

Categories

Resources