Stream groupingBy with flatmap - java

I have List<ServiceName> servicesNames and ServiceName has this structure:
public class ServiceName {
private Map<String, String> names;
}
Where names is a Hashmap with language and name - usually several values.
I'm trying to get Map<String lang, List<String> names>> - where key is language and value is a List of all the names in given language from List<ServiceName>.
Here is what I came by so far but can;t just put it together - getting compilation errors:
servicesNames
.stream()
.map(x -> x.getNames())
.flatMap(x -> x.entrySet().stream())
.collect(x -> Collectors.groupingBy());
EDIT:
I'm able to get this:
List<Map.Entry<String, String>> collect1 = servicesNames
.stream()
.map(x -> x.getNames())
.flatMap(x -> x.entrySet().stream())
.collect(Collectors.toList());
But don't know to to actually group by key all the values after using flatmap...

You need Collectors.groupingBy to group the values by a key and apply mapping to the downstream by Collectors.mapping.
The lambda-based approach:
Map<String, List<String>> map = servicesNames
.stream()
.flatMap(service -> service.getNames().entrySet().stream())
.collect(groupingBy(e -> e.getKey(), mapping(e -> e.getValue(), toList())));
The method reference-based approach:
Map<String, List<String>> map = servicesNames
.stream()
.map(ServiceName::getNames)
.map(Map::entrySet)
.flatMap(Set::stream)
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));

Related

How to group VALUES of same KEY of map in a List<Map<String,String>> in JAVA?

I have a List of Map in which there are many similar keys.
So i want to group the values of the same keys as a List<Map<String,List<String>>>
Note :- here the keys of the map are unknown
Input -
List<Map<String,String>> list = [{CLEANING=cleaning1}, {CLEANING=cleaning2}, {CLEANING=cleaning3}, {CLEANING=cleaning4}, {PAPER=paper1}, {PAPER=paper2}, {PAPER=paper3}, {PAPER=paper4}]
Output -
List<Map<String,List<String>>> outputList = [{CLEANING,[cleaning1,cleaning2,cleaning3,cleaning4]},{PAPER,[paper1,paper2,paper3,paper4]}]
Map<String,List<String>>
Check if key exists then append to string else add key and assign it a list with that string.
Is you want your list to be just use a set instead.
Not sure why do you need single entry maps in a list but it looks like the better structure as the result would be just a Map<String, List<String>>.
Here is a code that you could use using streams.
public static void main(String[] args) {
List<Map<String, String>> list = List.of(
Map.of("CLEANING", "1"),
Map.of("CLEANING", "2"),
Map.of("PAPER", "1")
);
List<Map<String, List<String>>> result1 = list.stream()
.flatMap(m -> m.entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())))
.entrySet().stream()
.map(e -> Map.of(e.getKey(), e.getValue()))
.collect(toList());
Map<String, List<String>> result2 = list.stream()
.flatMap(m -> m.entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));
System.out.println(result1); // [{CLEANING=[1, 2]}, {PAPER=[1]}]
System.out.println(result2); // {CLEANING=[1, 2], PAPER=[1]}
}

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

Java 8 Filter Map<String,List<Employee>>

How to filter a Map<String, List<Employee>> using Java 8 Filter?
I have to filter only when any of employee in the list having a field value Gender = "M".
Input: Map<String,List<Employee>>
Output: Map<String,List<Employee>>
Filter criteria: Employee.genter = "M"
Also i have to filter out the key in the output map (or filter map [new map we get after filter]) if the List<> is empty on the map value
To filter out entries where a list contains an employee who is not of the "M" gender:
Map<String, List<Employee>> r2 = map.entrySet().stream()
.filter(i -> i.getValue().stream().allMatch(e-> "M".equals(e.gender)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
To filter out employees who are not of the "M" gender:
Map<String, List<Employee>> r1 = map.entrySet().stream()
.filter(i -> !i.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey,
i -> i.getValue().stream()
.filter(e -> "M".equals(e.gender)).collect(Collectors.toList())));
To filter out entries where a list doesn't contain any "M" employee.
Map<String, List<Employee>> r3 = map.entrySet().stream()
.filter(i -> i.getValue().stream().anyMatch(e -> "M".equals(e.gender)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Let's have 2 entries in the map:
"1" -> ["M", "M", "M"]
"2" -> ["M", "F", "M"]
The results for them will be:
r1 = {1=[M, M, M], 2=[M, M]}
r2 = {1=[M, M, M]}
r3 = {1=[M, M, M], 2=[M, F, M]}
In Java 8 you can convert a Map.entrySet() into a stream, follow by a filter() and collect() it. Example taken from here.
Map<Integer, String> map = new HashMap<>();
map.put(1, "linode.com");
map.put(2, "heroku.com");
//Map -> Stream -> Filter -> String
String result = map.entrySet().stream()
.filter(x -> "something".equals(x.getValue()))
.map(x->x.getValue())
.collect(Collectors.joining());
//Map -> Stream -> Filter -> MAP
Map<Integer, String> collect = map.entrySet().stream()
.filter(x -> x.getKey() == 2)
.collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue()));
// or like this
Map<Integer, String> collect = map.entrySet().stream()
.filter(x -> x.getKey() == 3)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
And for your case it would look like this, because you also need to find out if there is a match in a List of object of class Employee.
Map<String, List<Employee>> collect = map.entrySet().stream()
.filter(x -> x.getValue().stream()
.anyMatch(employee -> employee.Gender.equals("M")))
.collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue()));
Map<String, List<Employee>> result = yourMap.entrySet()
.stream()
.flatMap(ent -> ent.getValue().stream().map(emp -> new SimpleEntry<>(ent.getKey(), emp)))
.filter(ent -> "M".equalsIgnoreCase(ent.getValue().getGender()))
.collect(Collectors.groupingBy(
Entry::getKey,
Collectors.mapping(Entry::getValue, Collectors.toList())));
If you want to filter the map entries if any Employee in the list of the entry has gender = M, use the following code:
Map<String,List<Employee>> result = employeeMap.entrySet()
.stream()
.filter(e -> e.getValue()
.stream()
.anyMatch(employee -> employee.getGender().equalsIgnoreCase("M")))
.collect(Collectors.toMap(Entry::getKey,Entry::getValue));
And, If you want to filter out all the Employees with gender M from each list, use the following code:
Map<String,List<Employee>> result = employeeMap.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey,
e -> e.getValue().stream()
.filter(employee -> employee.getGender().equalsIgnoreCase("M"))
.collect(Collectors.toList())));
Filter only map entries that have only male Employes:
#Test
public void filterOnlyMales(){
String gender = "M";
Map<String, List<Employee>> maleEmployees = map.entrySet()
.stream()
/*Filter only keys with male Employes*/
.filter(entry -> entry.getValue().stream()
.anyMatch(empl -> gender.equals(empl.getGender())))
.collect(Collectors.toMap(
Map.Entry::getKey,
p -> filterMalesOnly(gender, p));
}
private List<Employee> filterMalesOnly(String gender,
Map.Entry<String, List<Employee>> p) {
return p.getValue()
.stream()
.filter(empl -> gender.equals(empl.getGender()))
.collect(
Collectors.toList());
}
For instance:
Map<String, List<Employee>> result = originalMap.entrySet().stream()
.filter(es -> es.getValue().stream().anyMatch(emp -> emp.getGender().equals("M")))
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
Returning map of employee
public static Map<Integer, Employee> evaluatemapEmployee()
{
//return Dao.getselectedEmployee().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
//return Dao.getselectedEmployee().entrySet().stream().filter(emp->emp.getValue().getsalary()>8000).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
//return Dao.getselectedEmployee().entrySet().stream().filter(emp->emp.getValue().getEmpname().matches("om")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
//return Dao.getselectedEmployee().entrySet().stream().filter(emp->emp.getValue().getEmpid()==103).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return Dao.getselectedEmployee().entrySet().stream().filter(emp->emp.getValue().getEmpname().matches("kush")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

How do I convert a List<Entry> to Map where value is a list using streams?

How do I convert a List<Entry> to Map<Entry::getKey, List<Entry::getValue>> using streams in Java 8?
I couldn't come up with a good KeySelector for Collectors.toMap():
List<Entry<Integer, String>> list = Arrays.asList(Entry.newEntry(1, "a"), Entry.newEntry(2, "b"), Entry.newEntry(1, "c"));
Map<Integer, List<String>> map = list.stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
What I want to get: {'1': ["a", "c"], '2': ["b"]}.
You can do so by using the groupingBy collector along with mapping as the downstream collector:
myList.stream()
.collect(groupingBy(e -> e.getKey(), mapping(e -> e.getValue(), toList())));
import required:
import static java.util.stream.Collectors.*;
You can actually accomplish the same result with the toMap collector:
myList.stream()
.collect(toMap(e -> e.getKey(),
v -> new ArrayList<>(Collections.singletonList(v.getValue())),
(left, right) -> {left.addAll(right); return left;}));
but it's not ideal when you can use the groupingBy collector and it's less readable IMO.

Java 8 : grouping field values by field names

I'm trying to find a more elegant way to create a map that group field values by field names using Java 8 than the following:
#Test
public void groupFieldValuesByFieldNames() {
Person lawrence = aPerson().withFirstName("Lawrence").withLastName("Warren").born();
Person gracie = aPerson().withFirstName("Gracie").withLastName("Ness").born();
Map<String, List<String>> valuesByFieldNames = new HashMap<>();
Stream.of(lawrence, gracie).forEach(person -> {
valuesByFieldNames.computeIfAbsent("lastName", s -> new ArrayList<>()).add(person.getLastName());
valuesByFieldNames.computeIfAbsent("firstName", s -> new ArrayList<>()).add(person.getFirstName());
});
assertThat(valuesByFieldNames, hasEntry("lastName", asList("Warren", "Ness")));
assertThat(valuesByFieldNames, hasEntry("firstName", asList("Lawrence", "Gracie")));
}
Try this.
Map<String, List<String>> valuesByFieldNames = Stream.of(lawrence, gracie)
.flatMap(p -> Stream.of(new String[]{"firstName", p.getFirstName()},
new String[]{"lastName", p.getLastName()}))
.collect(Collectors.groupingBy(a -> a[0],
Collectors.mapping(a -> a[1], Collectors.toList())));
Or more generally
Map<String, List<String>> valuesByFieldNames = Stream.of(lawrence, gracie)
.flatMap(p -> Stream.of(new AbstractMap.SimpleEntry<>("firstName", p.getFirstName()),
new AbstractMap.SimpleEntry<>("lastName", p.getLastName())))
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.mapping(e -> e.getValue(), Collectors.toList())));
You can have the following, that will work correctly in parallel:
Map<String, List<String>> valuesByFieldNames =
Stream.of(lawrence, gracie).collect(HashMap::new, (m, p) -> {
m.computeIfAbsent("lastName", s -> new ArrayList<>()).add(p.getLastName());
m.computeIfAbsent("firstName", s -> new ArrayList<>()).add(p.getFirstName());
}, (m1, m2) -> m2.forEach((k, v) -> m1.merge(k, v, (l1, l2) -> { l1.addAll(l2); return l1; })));
What this does is that it collect each person into a mutable HashMap. The accumulator computes the last name and the first name by invoking computeIfAbsent, just like your initial code. The combiner merges two maps together by iterating over the entries of the second map and merging each key into the first map; in case of conflict, the value is the addition of the two lists.

Categories