How to convert Stream<Map<Integer, String>> to map java 8 - java

Here I am posting sample datastructure
I have a list List<Result> resultsList;
class Result {
String name;
Map<String,Integer> resultMap;
}
Now I would like to stream through this list and get the map.
resultList.stream().filter(result->"xxx".equals(result.getName()))
.map(result->result.getResultMap);
It returns Stream<Map<String,Integer>> but I need only Map<String,Integer>.
How to get it using java 8 streams?
Update:
As geneqew mentioned
This is how my datastructure looks
List<Result> resultsList;
Map<String, Integer> map1 = new HashMap<>();
map1.put("m1", 1);
Map<String, Integer> map2 = new HashMap<>();
map2.put("m2", 2);
Map<String, Integer> map3 = new HashMap<>();
map3.put("m3", 3);
results = Arrays.asList(
new Result("r1", map1),
new Result("r2", map2),
new Result("r3", map3)
);
I would like to retrieve single map based on name.
for (Result result: resultsList)
{
if ('xxx'.equals(result.getName())
{
return result.getResultMap();
}
}

Since you want to return the result map of the first Result element to pass your filter, you can obtain it with findFirst():
Optional<Map<String,Integer>> resultMap =
resultList.stream()
.filter(result->"xxx".equals(result.getName()))
.map(Result::getResultMap)
.findFirst();
You can extract the Map from the Optional this way:
Map<String,Integer> resultMap =
resultList.stream()
.filter(result->"xxx".equals(result.getName()))
.map(Result::getResultMap)
.findFirst()
.orElse(null);

if you're only looking for one item:
resultList.stream()
.filter(result -> "xxx".equals(result.getName()))
.map(Result::getResultMap)
.findAny();
if the filter could match more than one item then you'll need to flatten then toMap it:
resultList.stream()
.filter(result-> "xxx".equals(result.getName()))
.flatMap(result -> result.getResultMap().entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
if there can be duplicates then use the merge function to resolve collisions:
resultList.stream()
.filter(result -> "xxx".equals(result.getName()))
.flatMap(result -> result.getResultMap().entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (l, r) -> l));

Since you only wanted the map that matches the results' name then:
results.stream()
.filter(r-> r.getName().equals("r2"))
.map(r-> r.getResultMap())
.findFirst()
.orElse(null);
given you have a sample content of:
List<Result> results;
Map<String, Integer> map1 = new HashMap<>();
map1.put("m1", 1);
Map<String, Integer> map2 = new HashMap<>();
map2.put("m2", 2);
Map<String, Integer> map3 = new HashMap<>();
map3.put("m3", 3);
results = Arrays.asList(
new Result("r1", map1),
new Result("r2", map2),
new Result("r3", map3)
);
A bit of explanation, you got a stream because the last operation in your stream is a map; assuming in your list its possible to have more than 1 result with the same name, findFirst will return the first match if found otherwise an empty optional is returned; Finally orElse to get terminate the stream, providing a null value on empty match.

So I want to explain why you receive stream and not a map. The reason of this is because in the beginning you have List with Result objects that you filter by some criteria (in your case "xxx".equals(result.getName())).
Now you can have as result zero, one or more elements that will pass this criteria! Java does not know how many elements will pass at compile time and that is why you get Stream.
Imagine situation that you have two Result objects that have the same name 'xxx' then you will have two maps. The question is what you want to do? If you get only one of the maps you will loose information. If you want to get all of them, please try something like this:
List<Map<String,Integer>> listWithResultMaps = resultList.stream()
.filter(result->"xxx".equals(result.getName()))
.map(result->result.getResultMap())
.collect(Collectors.toList());
Now in this listWithResultMaps you can process all maps that you have as result of your filter.
Good Luck!

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

Map objects by occurrences in list of pairs

I need to map a list of pairs of objects into <ocurrences, list of Objs with those ocurrences>, I've tried using streams directly on the input list of pairs but I'm still kind of new to java and couldn't figure it out, so I was trying to do something like this, but it's probably not close to the best way to do it.
public Map<Integer,ArrayList<Obj>> numBorders(List<Pair<Obj,Obj>> lf) {
Map<Integer,ArrayList<Obj>> nBorders = new HashMap<>();
List<Obj> list = new ArrayList<>();
for(Pair<Obj, Obj> pair : lf) {
list.add(pair.getKey());
list.add(pair.getValue());
}
nBorders = list.stream().collect(Collectors.groupingBy(...);
return nBorders;
}
so for example, for lf = {(o1,o2),(o3,o2),(o5,o4),(o4,o1),(o3,o4),(o7,o1),(o5,o8),(o3,o10),(o4,o5),(o3,o7),(o9,o8)} the result should be {(1,{o9,o10}),(2,{o2,o7,o8,}),(3,{o1,o5}),(4,{o3,o4})}.
I'm really confused on how to do this, if someone could help, I'd appreciate it, thanks.
This can be done this way:
create a stream from the pairs to concatenate first/second values using Stream::flatMap
count the occurrences - build an intermediate map <Obj, Integer> using Collectors.groupingBy + Collectors.summingInt (to keep integer)
create an inverse map <Integer, List> from the stream of the entries in the intermediate map using Collectors.groupingBy + Collectors.mapping
Optionally, if an order in the resulting map is critical, a LinkedHashMap may be created from the entries of the intermediate frequency map sorted by value.
public Map<Integer,ArrayList<Obj>> numBorders(List<Pair<Obj,Obj>> lf) {
return lf.stream() // Stream<Pair>
.flatMap(p -> Stream.of(p.getKey(), p.getValue())) // Stream<Obj>
.collect(Collectors.groupingBy(
obj -> obj,
Collectors.summingInt(obj -> 1)
)) // Map<Obj, Integer>
.entrySet()
.stream() // Stream<Map.Entry<Obj, Integer>>
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.groupingBy(
Map.Entry::getValue, // frequency is key
LinkedHashMap::new,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
)); // Map<Integer, List<Obj>>
}

Convert java list to map using stream with indexes

I'm trying to learn how to use the Java 8 collections and I was wondering if there was a way to convert my list to a map using a java stream.
List<PrimaryCareDTO> batchList = new ArrayList<>();
PrimaryCareDTO obj = new PrimaryCareDTO();
obj.setProviderId("123");
obj.setLocatorCode("abc");
batchList.add(obj);
obj = new PrimaryCareDTO();
obj.setProviderId("456");
obj.setLocatorCode("def");
batchList.add(obj);
I'm wondering how I would go about creating my list above into a map using a stream. I know how to use the foreach etc with puts, but I was just wondering if there was a more elegant way to build the map using a stream. (I'm aware the syntax below is not correct, I'm new to streams and not sure how to write it)
AtomicInteger index = new AtomicInteger(0);
Map<String, Object> result = batchList.stream()
.map("providerId" + index.getAndIncrement(), PrimaryCareDTO::getProviderId)
.map("locatorCode" + index.get(), PrimaryCareDTO::getLocatorCode);
The goal is to represent the following.
Map<String, Object> map = new HashMap<>();
//Group a
map.put("providerId1", "123");
map.put("locatorCode1", "abc");
//Group b
map.put("providerId2", "456");
map.put("locatorCode2", "def");
...
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
...
AtomicInteger index = new AtomicInteger(0);
List<SimpleEntry<String, String>> providerIds =
batchList.stream()
.map(e -> new SimpleEntry<>("providerId" + index.incrementAndGet(), e.getProviderId()))
.collect(Collectors.toList());
index.set(0);
List<SimpleEntry<String, String>> locatorCodes =
batchList.stream()
.map(e -> new SimpleEntry<>("locatorCode" + index.incrementAndGet(), e.getLocatorCode()))
.collect(Collectors.toList());
Map<String, String> map = Stream.of(providerIds,
locatorCodes)
.flatMap(e -> e.stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
First it creates two lists, using Entry (from Map) to represent String-String tuples:
list with tuples providerId# as 'key' with the values e.g. "123"
list with tuples locatorCode# as 'key' with the values e.g. "abc"
It then creates a stream containing these two lists as 'elements', which are then concatenated with flatMap() to get a single long stream of Entry,
(The reason the first two can't stay stream and I have to go through a List and back to stream is because the two invocations of index.incrementAndGet() would otherwise only be evaluated when the streams are consumed, which is after index.set(0);.)
It then creates new key-value pairs with the counter and puts them into a map (with Collectors.toMap().
You would have to steam twice as you want to add two of the properties to map
AtomicInteger index = new AtomicInteger(1);
Map<String, String> result1 = batchList.stream()
.collect(Collectors
.toMap(ignored -> "providerId" + index.getAndIncrement(), PrimaryCareDTO::getProviderId)
);
index.set(1);
Map<String, String> result2 = batchList.stream()
.collect(Collectors
.toMap(ignored -> "locatorCode" + index.getAndIncrement(), PrimaryCareDTO::getLocatorCode)
);
Map<String, String> result = new HashMap<>();
result.putAll(result1);
result.putAll(result2);

Using java streams, merge two maps having same keys but different values to a Tuple?

I have two maps with the following data type,
Map<Pair<Long,String>, List<String>> stringValues;
Map<Pair<Long,String>, List<Boolean>> booleanValues ;
I want to merge the above maps to the following datastructure
Map<Pair<Long,String>, Pair<List<String>,List<Boolean>>> stringBoolValues;
My input has two maps with same key but different values. I want to group them to a pair. Can I use java stream to achieve this ?
other simple way is like this:
stringValues.forEach((key, value) -> {
Pair<List<String>, List<Boolean>> pair = new Pair<>(value, booleanValues.get(key));
stringBoolValues.put(key, pair);
});
stringBoolValues = stringValues
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
entry -> new Pair<>(entry.getValue(), booleanValues.get(entry.getKey()))));
Try like this:
Set<Pair<Long,String>> keys = new HashSet<>(stringValues.keySet());
keys.addAll(booleanValues.keySet());
keys.stream().collect(Collectors.toMap(key -> key,
key -> new Pair<>(stringValues.get(key), booleanValues.get(key))));
Precondition: You had overridden equals()/hashCode() properly for Pair<Long, String>
Map<Pair<Long,String>, Pair<List<String>,List<Boolean>>> stringBoolValues
= Stream.of(stringValues.keySet(),booleanValues.keySet())
.flatMap(Set::stream)
.map(k -> new SimpleEntry<>(k, Pair.of(stringValues.get(k), booleanValues.get(k)))
.collect(toMap(Entry::getKey, Entry::getValue));
Where Pair.of is:
public static Pair<List<String>,List<Boolean>> of(List<String> strs, List<Boolean> bls) {
List<String> left = Optional.ofNullable(strs).orElseGet(ArrayList::new);
List<Boolean> right = Optional.ofNullable(bls).orElseGet(ArrayList::new);
return new Pair<>(left, right);
}
You can even use Map.computeIfAbsent to avoid the need of explicit checking for null.
Just using the valueMapper in Collectors.toMap to merge values in two different maps easily:
Map<Pair<Long, String>, Pair<List<String>, List<Boolean>>> stringBoolValues = stringValues.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> new Pair(entry.getValue(), booleanValues.get(entry.getKey()))));

Java 8 groupingby Into map that contains a list

I have the following data:
List<Map<String, Object>> products = new ArrayList<>();
Map<String, Object> product1 = new HashMap<>();
product1.put("Id", 1);
product1.put("number", "123");
product1.put("location", "ny");
Map<String, Object> product2 = new HashMap<>();
product2.put("Id", 1);
product2.put("number", "456");
product2.put("location", "ny");
Map<String, Object> product3 = new HashMap<>();
product3.put("Id", 2);
product3.put("number", "789");
product3.put("location", "ny");
products.add(product1);
products.add(product2);
products.add(product3);
I'm trying to stream over the products list, group by the id and for each id have a list on number, while returning a Map that contains three keys: Id, List of number, and a location.
So my output would be:
List<Map<String, Object>>> groupedProducts
map[0]
{id:1, number[123,456], location:ny}
map[1]
{id:2, number[789], location:ny}
I have tried:
Map<String, List<Object>> groupedProducts = products.stream()
.flatMap(m -> m.entrySet().stream())
.collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toList())));
which prints:
{number=[123, 456, 789], location=[ny, ny, ny], Id=[1, 1, 2]}
I realise Map<String, List<Object>> is incorrect, but it's the best I could achieve to get the stream to work. Any feedback is appreciated.
In your case grouping by Id key with Collectors.collectingAndThen(downstream, finisher) could do the trick. Consider following example:
Collection<Map<String, Object>> finalMaps = products.stream()
.collect(groupingBy(it -> it.get("Id"), Collectors.collectingAndThen(
Collectors.toList(),
maps -> (Map<String, Object>) maps.stream()
.reduce(new HashMap<>(), (result, map) -> {
final List<Object> numbers = (List<Object>) result.getOrDefault("number", new ArrayList<>());
result.put("Id", map.getOrDefault("Id", result.getOrDefault("Id", null)));
result.put("location", map.getOrDefault("location", result.getOrDefault("location", null)));
if (map.containsKey("number")) {
numbers.add(map.get("number"));
}
result.put("number", numbers);
return result;
}))
)
)
.values();
System.out.println(finalMaps);
In the first step you group all maps with the same Id value to a List<Map<String,Object>> (this is what Collectors.toList() passed to .collectingAndThen() does). After creating that list "finisher" function is called - in this case we transform list of maps into a single map using Stream.reduce() operation - we start with an empty HashMap<String,Object> and we iterate over maps, take values from current map in iteration and we set values according to your specification ("Id" and "location" gets overridden, "number" keeps a list of values).
Output
[{number=[123, 456], location=ny, Id=1}, {number=[789], location=ny, Id=2}]
To make code more simple you can extract BiOperator passed to Stream.reduce to a method and use method reference instead. This function defines what does it mean to combine two maps into single one, so it is the core logic of the whole reduction.

Categories

Resources