Java 8 : grouping field values by field names - java

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.

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

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

Stream groupingBy with flatmap

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

How to get all value from List of Map in Java?

I have a List of Map:
List(Map(term -> check), Map(term -> base), Map(term -> prof.gaurav),
Map(term -> phone), Map(term -> scraper), Map(term -> with),
Map(term -> certificate), Map(term -> six), Map(term -> itself))
And I want to get all values and check if a term already exists in above list of Map.
This can be done by iterating the List and checking the value of each map. But I want something more efficient and one liner.
This question may be naive, but I am not getting how to proceed. I am new to Java.
Expected Output:
List(check, base, prof.gaurav, phone, scraper, with, certificate, six, itself)
If I understand your question correct you want to check if any of these terms contain a tag?
// To check if any of them contains
boolean anyContains = list.stream().any(m -> m.contains("term"));
// get string which did not have value
list<String> nonUsedTerms = list.stream
.filter(m -> !m.contains(term))
.map(m -> m.get(term));
Given term defines the term you like to check for existence, you may do it this way:
list.stream()
.flatMap(map -> map.values().stream())
.anyMatch(value -> value.equals(term));
Replace .anyMatch(...) with .collect(Collectors.toList()) and you get your list. But you stated earlier in your question you like to check if a term is present.
As a first step we can transform List[ Map(term -> check), Map(term -> base) ] to a Map( term -> List[check,base] ) and then we can extract that List[check,base] using Map#getOrDefault method:
HashMap<String, String> map = new HashMap<>();
map.put("lorem","ipsum");
HashMap<String, String> map1 = new HashMap<>();
map1.put("lorem","dolor");
HashMap<String, String> map2 = new HashMap<>();
map2.put("sit","amet");
HashMap<String, String> map3 = new HashMap<>();
map3.put("sit","consectetur");
List<Map<String,String>> list = Arrays.asList(map, map1, map2, map3);
Map<String, List<String>> result = list.stream().
flatMap(m -> m.entrySet().stream()).
collect(
groupingBy(
Map.Entry::getKey,
mapping(Map.Entry::getValue, toList())
)
); // {lorem=[ipsum, dolor], sit=[amet, consectetur]}
result.getOrDefault("lorem", Collections.emptyList()); // [ipsum, dolor]

Java stream - find unique elements

I have List<Person> persons = new ArrayList<>(); and I want to list all unique names. I mean If there are "John", "Max", "John", "Greg" then I want to list only "Max" and "Greg". Is there some way to do it with Java stream?
We can use streams and Collectors.groupingBy in order to count how many occurrences we have of each name - then filter any name that appears more than once:
List<String> res = persons.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.filter(e -> e.getValue() == 1)
.map(e -> e.getKey())
.collect(Collectors.toList());
System.out.println(res); // [Max, Greg]
List persons = new ArrayList();
persons.add("Max");
persons.add("John");
persons.add("John");
persons.add("Greg");
persons.stream()
.filter(person -> Collections.frequency(persons, person) == 1)
.collect(Collectors.toList());
First guess solution.
persons.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.filter(entry -> entry.getValue() == 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList())
Here is my solution:
List<String> persons = new ArrayList<>();
persons.add("John");
persons.add("John");
persons.add("MAX");
persons.add("Greg");
persons.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
This is an old post, but I'd like to propose yet another approach based on a custom collector:
public static <T> Collector<T, ?, List<T>> excludingDuplicates() {
return Collector.<T, Map<T, Boolean>, List<T>>of(
LinkedHashMap::new,
(map, elem) -> map.compute(elem, (k, v) -> v == null),
(left, right) -> {
right.forEach((k, v) -> left.merge(k, v, (o, n) -> false));
return left;
},
m -> m.keySet().stream().filter(m::get).collect(Collectors.toList()));
}
Here I'm using Collector.of to create a custom collector that will accumulate elements on a LinkedHashMap: if the element is not present as a key, its value will be true, otherwise it will be false. The merge function is only applied for parallel streams and it merges the right map into the left map by attempting to put each entry of the right map in the left map, changing the value of already present keys to false. Finally, the finisher function returns a list with the keys of the map whose values are true.
This method can be used as follows:
List<String> people = Arrays.asList("John", "Max", "John", "Greg");
List<String> result = people.stream().collect(excludingDuplicates());
System.out.println(result); // [Max, Greg]
And here's another approach simpler than using a custom collector:
Map<String, Boolean> duplicates = new LinkedHashMap<>();
people.forEach(elem -> duplicates.compute(elem, (k, v) -> v != null));
duplicates.values().removeIf(v -> v);
Set<String> allUnique = duplicates.keySet();
System.out.println(allUnique); // [Max, Greg]
You can try the below code.
List<Person> uniquePersons = personList.stream()
.collect(Collectors.groupingBy(person -> person.getName()))
.entrySet().stream().filter(stringListEntry -> stringListEntry.getValue().size()==1)
.map(stringListEntry -> { return stringListEntry.getValue().get(0); })
.collect(Collectors.toList());
This should remove all the duplicate elements.
List<String> persons = new ArrayList<>();
persons.add("John");
persons.add("John");
persons.add("MAX");
persons.add("Greg");
Set<String> set = new HashSet<String>();
Set<String> duplicateSet = new HashSet<String>();
for (String p : persons) {
if (!set.add(p)) {
duplicateSet.add(p);
}
}
System.out.println(duplicateSet.toString());
set.removeAll(duplicateSet);
System.out.println(set.toString());
You can simply use Collections.frequency to check the element occurance in the list as shown below to filter the duplicates:
List<String> listInputs = new ArrayList<>();
//add your users
List<String> listOutputs = new ArrayList<>();
for(String value : listInputs) {
if(Collections.frequency(listInputs, value) ==1) {
listOutputs.add(value);
}
}
System.out.println(listOutputs);

Categories