How create HashMap from flatMap? - java

I'm have two Maps in method param.
private Map<String, List<Attr>> getPropAttr(Map<String, List<Attr>> redundantProperty,
Map<String, List<Attr>> notEnoughProperty) {
Map<String, List<Attr>> propAttr = new HashMap<>();
redundantProperty.forEach((secondPropertyName, secondPropertyAttributes) -> notEnoughProperty.entrySet().stream()
.filter(firstPropertyName -> secondPropertyName.contains(firstPropertyName.getKey()))
.forEach(firstProperty -> {
List<Attr> firstPropertyAttrs = firstProperty.getValue();
List<Attr> redundantPropAttrs = getRedundantPropAttrs(secondPropertyAttrs, firstPropertyAttrs);
String propName = firstProperty.getKey();
propAttr.put(propertyName, redundantPropAttrs);
}));
return propAttr;
I want to rewrite this method on stream. But, i have some problems in stream collectors. It's don't see return value(List ) from stream into flatmap. In below - my attempt to rewrite this method on stream API. How set second param in collect(toMap(first::get, second::get))?
Thank you an advance.
private Map<String, List<Attr>> getPropAttr(Map<String, List<Attr>> redundantProperty,
Map<String, List<Attr>> notEnoughProperty) {
return redundantProperty.entrySet().stream()
.flatMap(secondProperty -> notEnoughProperty.entrySet().stream()
.filter(firstPropertyName -> secondProperty.getKey().contains(firstPropertyName.getKey()))
.map(firstProperty -> {
List<Attr> onlinePropertyAttrs = firstProperty.getValue();
List<Attr> redundantPropAttrs =
getRedundantPropAttrs(secondProperty.getValue(), firstPropertyAttrs);
return redundantPropertyAttrs;
}))
.collect(toMap(Property::getName, toList()));

After your flatMap call, your Stream becomes a Stream<List<Attr>>. It looks like you lose the property you want to use as a key for the output Map at this point.
Instead, I suggest that the map inside the flatMap return a Map.Entry containing the required key and value:
return redundantProperty.entrySet()
.stream()
.flatMap(secondProperty ->
notEnoughProperty.entrySet()
.stream()
.filter(firstPropertyName -> secondProperty.getKey().contains(firstPropertyName.getKey()))
.map(firstProperty -> {
List<Attr> redundantPropAttrs = ...
...
return new SimpleEntry<String,List<Attr>>(firstProperty.getKey(),redundantPropertyAttrs);
}))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

Related

Merge Two Lists based on a condition and push the result to a map using java 8

I have two lists source and target want to merge them based on some condition and push the data to Hashmap. I tried below code but i could not succeed.
public List<Persona> fetchCommonPersonas(List<User> sourceList,
List<User> targetList) {
final Map<String, String> map = new HashMap<>();
map = sourceList.stream()
.filter(source -> targetList.stream().anyMatch(destination -> {
if(destination.getAge().equals(source.getAge())) {
map.put(source.getUserId(), destination.getUserId());
}
}
));
}
Here's one way of doing it:
Map<String, String> map =
sourceList.stream()
.map(source -> targetList.stream()
.filter(dest -> dest.getUserId().equals(source.getUserId()))
.map(dest -> new SimpleEntry<>(source.getPersonaId(), dest.getPersonaId()))
.firstFirst())
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
You find for each element of the source list a corresponding element of the target list, map these elements to a Map.Entry that contains the two person Ids, and collect all the entries to a Map.
You can utilize a groupingBy of the source list to look up for the data in the second stage and then collect the target and source id pairs as follows -
Map<Integer, List<String>> sourceGrouping = sourceList.stream()
.collect(Collectors.groupingBy(User::getAge,
Collectors.mapping(User::getId, Collectors.toList())));
Map<String, String> map = targetList.stream()
.filter(u -> sourceGrouping.containsKey(u.getAge()))
.flatMap(u -> sourceGrouping.get(u.getAge())
.stream().map(s -> new AbstractMap.SimpleEntry<>(s, u.getId())))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue));
After i got inputs from Eran this the final piece of code
Map<String, String> commonMap = sourceList.stream()
.flatMap(source -> targetList.stream()
.filter(target -> source.getUserId().equals(target.getUserId()))
.map(target -> new AbstractMap.SimpleImmutableEntry<>(sourcePersona.getPersonaId(), targetPersona.getPersonaId())))
.filter(immutableEntry -> (immutableEntry != null
&& StringUtils.isNotBlank(immutableEntry.getKey()) && StringUtils.isNotBlank(immutableEntry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Java8 How to convert 3-level nested list into nested HashMap using stream and lambda

I'm trying to convert a 3 level nested list into Nested HashMap.
The function declaration for the same is:
Map<Key1, Map<Key2, List<String>>> transformToMap (List<Obj1> inputList)
The inputList internally has nested list which again has nested list.
The code I've wrote is using traditional for loop as follow:
private Map<Key1 , Map<Key2, List<String>>> reverseLookup(List<Key2> key2List){
Map<Key1 , Map<Key2, List<String>>> resultMap = new HashMap<>();
key2List.forEach(key2->{
List<ElementObject> elementObjects = key2.getElementObjects();
elementObjects.forEach(elementObject->{
final String name = elementObject.getName();
elementObject.getApplicablePeriods().forEach(applicablePeriod-> {
Key1 key1 = applicablePeriod.getKey1();
Map<Key2, List<String>> map2 = resultMap.get(key1);
if(map2 == null){
map2 = new HashMap<>();
}
List<String> stringList = map2.get(key2);
if(stringList == null){
stringList = new ArrayList<>();
}
stringList.add(name);
map2.put(key2, stringList);
resultMap.put(key1, map2);
});
});
});
return resultMap;
}
The class structure for the same is as follow:
class Key2{
List<ElementObject> elementObjects;
//getters & setters
}
class ElementObject {
String name;
//few more params
List<ApplicablePeriod> applicablePeriods;
//getters & setters
}
class ApplicablePeriod{
Key1 key1;
//getters & setters
}
class Key1{
//some parameters
//getters & setters
}
The above code is fulfilling my expectations.
What will be the efficient way to transform it into stream lambda using Collectors.toMap ?
I've tried something as follow:
inputList
.stream()
.flatMap(item -> item.getObj2List().stream())
.flatMap(nestedItem -> nestedItem.getKeyList().stream())
.collect(Collectors.toMap(a-> a.get()))
But not getting what should be the next step in Collectors.toMap.
Not able to handle final String name = nestedItem.getName(); which is used just before 3rd for loop.
Let me know the way to solve this.
I don't have any test data to see if it creates something similar as your traditional code. But take a look at this and let me know if it helps:
key2List.stream().flatMap((key2) -> key2.elementObjects.stream().map((element) -> new AbstractMap.SimpleImmutableEntry<>(key2, element)))
.flatMap((entry) -> entry.getValue().applicablePeriods.stream().map((period) -> new AbstractMap.SimpleImmutableEntry<>(period.key1, new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue().name))))
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())))));
In fact, the problem is that you want to access multiple levels of abstractions inside of the same Stream, this is usually not possible unless you have
Nested streams
An object that can hold references to higher objects
I fixed it in the second way.
I'm using java-16 and the following features
Collectors#mapMulti
Collectors#groupingBy
A locally defined record
private Map<Key1, Map<Key2, List<String>>> reverseLookup(List<Key2> key2List) {
record HolderObject(Key2 key2, ElementObject elementObject, ApplicablePeriod applicablePeriod){}
return key2List.stream()
.mapMulti((Key2 key2, Consumer<HolderObject> consumer) -> {
List<ElementObject> elementObjects = key2.getElementObjects();
elementObjects.forEach(elementObject ->
elementObject.getApplicablePeriods().forEach(applicablePeriod -> {
consumer.accept(new HolderObject(
key2,
elementObject,
applicablePeriod
));
})
);
})
.collect(Collectors.groupingBy(
h -> h.applicablePeriod().getKey1(),
Collectors.groupingBy(
HolderObject::key2,
Collectors.mapping(
h -> h.elementObject().getName(),
Collectors.toList()
)
)
));
}
And here is a java-8 compatible solution
private Map<Key1, Map<Key2, List<String>>> reverseLookup(List<Key2> key2List) {
return key2List
.stream()
.flatMap(key2 -> key2.getElementObjects()
.stream()
.flatMap(elementObject -> elementObject.getApplicablePeriods()
.stream()
.map(applicablePeriod -> new HolderObject(
key2,
elementObject,
applicablePeriod
))))
.collect(Collectors.groupingBy(
h -> h.getApplicablePeriod().getKey1(),
Collectors.groupingBy(
HolderObject::getKey2,
Collectors.mapping(
h -> h.getElementObject().getName(),
Collectors.toList()
)
)
));
}
#Value
public static class HolderObject {
Key2 key2;
ElementObject elementObject;
ApplicablePeriod applicablePeriod;
}

Using stream to convert list of strings to list of maps with custom key

Given list of strings like this:
"Y:Yes",
"N:No",
"A:Apple"
I have something like
Map updated = values.stream().map(v -> v.split(":")).collect(Collectors.toMap(v1 -> v1[0],v1->v1.length>1?v1[1]:v1[0]));
But this gives me map as:
{
"Y":"Yes",
"N":"No",
"A":"Apple"
}
How can I get a list of maps as such:
[
{
name:"Y",
display:"Yes"
},
{
name:"N",
display:"No"
},
{
name:"A",
display:"Apple"
}
]
If you are using Java 9, you can use the new immutable map static factory methods, as follows:
List<Map<String, String>> updated = values.stream()
.map(v -> v.split(":"))
.map(a -> Map.of("name", a[0], "display", a[1]))
.collect(Collectors.toList());
As you want to get a List, not a map, your last function call cannot be Collectors.toMap, needs to be Collectors.toList. Now, each invocation to the map method should generate a new Map, so something like this would do:
List updated = values.stream()
.map(v -> {
String[] parts = v.split(":");
Map<String, String> map = new HashMap<>();
map.put("name", parts[0]);
map.put("display", parts[1]);
return map;
)
.collect(Collectors.toList());
Some people would prefer:
List updated = values.stream()
.map(v -> {
String[] parts = v.split(":");
return new HashMap<>() {{
put("name", parts[0]);
put("display", parts[1]);
}};
)
.collect(Collectors.toList());
which creates an extra helper class. Or if you can use Guava:
List updated = values.stream()
.map(v -> {
String[] parts = v.split(":");
return ImmutableMap.of("name", parts[0], "display", parts[1]);
)
.collect(Collectors.toList());
BTW: In the examples I used Listbut the complete type of what you describe would be List<Map<String, String>>.
You can use following if you're still using Java8, if you happen to use Java9 then have a look at Federicos answer:
final List<Map<String,String>> updated = values.stream()
.map(v -> v.split(":"))
.map(arr -> {
Map<String, String> map = new HashMap<>();
map.put("name", arr[0]);
map.put("display", arr[1]);
return map;
})
.collect(Collectors.toList());

Process list stream and collect into map/ImmutableMap with only non null values

How to process a list of string and collec it into Map or Immutable map only for those whose value is present
String anotherParam = "xyz";
Map.Builder<String,String> resultMap = ImmutableMap.builder(..)
listOfItems.stream()
.filter(Objects::nonNull)
.distinct()
.forEach(
item -> {
final Optional<String> result =
getProcessedItem(item,anotherParam);
if (result.isPresent()) {
resultMap.put(item, result.get());
}
});
return resultMap.build();
Please tell, is there a better way to achieve this via collect?
If you have access to Apache Commons library you can make use of Pair.class
Map<String, String> resultMap = ImmutableMap.copyof(listOfItems()
.stream()
.filter(Objects::nonNull)
.distinct()
.map(it -> Pair.of(it, getProcessedItem(it,anotherParam))
.filter(pair -> pair.getValue().isPresent())
.collect(toMap(Pair::getKey, pair -> pair.getValue().get())))
But it's a good practice to make special data classes which describes your mapping item->result more specificly
Here is an example, create class like this:
static class ItemResult(){
public final String item;
public final Optional<String> result;
public ItemResult(String item, Optional<String> result){
this.item = item;
this.result = result;
}
public boolean isPresent(){
return this.result.isPresent();
}
public String getResult(){
return result.get();
}
}
And use it like that:
Map<String, String> resultMap = ImmutableMap.copyOf(listOfItems()
.stream()
.filter(Objects::nonNull)
.distinct()
.map(it -> new ItemResult(it, getProcessedItem(it,anotherParam))
.filter(ItemResult::isPresent)
.collect(toMap(ItemResult::item, ItemResult::getResult)))
You can read here why Google gave up the idea of tuples and pairs and don't use them in most cases
If after all you don't want to use any other class you can leverage api of the Optional:
Map.Builder<String,String> resultMap = ImmutableMap.builder(..)
listOfItems.stream()
.filter(Objects::nonNull)
.distinct()
.forEach(item -> getProcessedItem(item,anotherParam)
.ifPresent(result -> resultMap.put(item result));
return resultMap.build();

Java Streams: group a List into a Map of Maps

How could I do the following with Java Streams?
Let's say I have the following classes:
class Foo {
Bar b;
}
class Bar {
String id;
String date;
}
I have a List<Foo> and I want to convert it to a Map <Foo.b.id, Map<Foo.b.date, Foo>. I.e: group first by the Foo.b.id and then by Foo.b.date.
I'm struggling with the following 2-step approach, but the second one doesn't even compile:
Map<String, List<Foo>> groupById =
myList
.stream()
.collect(
Collectors.groupingBy(
foo -> foo.getBar().getId()
)
);
Map<String, Map<String, Foo>> output = groupById.entrySet()
.stream()
.map(
entry -> entry.getKey(),
entry -> entry.getValue()
.stream()
.collect(
Collectors.groupingBy(
bar -> bar.getDate()
)
)
);
Thanks in advance.
You can group your data in one go assuming there are only distinct Foo:
Map<String, Map<String, Foo>> map = list.stream()
.collect(Collectors.groupingBy(f -> f.b.id,
Collectors.toMap(f -> f.b.date, Function.identity())));
Saving some characters by using static imports:
Map<String, Map<String, Foo>> map = list.stream()
.collect(groupingBy(f -> f.b.id, toMap(f -> f.b.date, identity())));
Suppose (b.id, b.date) pairs are distinct. If so,
in second step you don't need grouping, just collecting to Map where key is foo.b.date and value is foo itself:
Map<String, Map<String, Foo>> map =
myList.stream()
.collect(Collectors.groupingBy(f -> f.b.id)) // map {Foo.b.id -> List<Foo>}
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(), // id
e -> e.getValue().stream() // stream of foos
.collect(Collectors.toMap(f -> f.b.date,
f -> f))));
Or even more simple:
Map<String, Map<String, Foo>> map =
myList.stream()
.collect(Collectors.groupingBy(f -> f.b.id,
Collectors.toMap(f -> f.b.date,
f -> f)));
An alternative is to support the equality contract on your key, Bar:
class Bar {
String id;
String date;
public boolean equals(Object o){
if (o == null) return false;
if (!o.getClass().equals(getClass())) return false;
Bar other = (Bar)o;
return Objects.equals(o.id, id) && Objects.equals(o.date, date);
}
public int hashCode(){
return id.hashCode*31 + date.hashCode;
}
}
Now you can just have a Map<Bar, Foo>.

Categories