Reputation: 385
Is there any way to filter a Set field in a Stream or List based on a Set using Lambda Stream?
Example:
List<Person> persons = new ArrayList<Person>();
Set<String> hobbySet1 = new HashSet<String>();
hobbySet1.add("H1");
hobbySet1.add("H2");
hobbySet1.add("H3");
Set<String> hobbySet2 = new HashSet<String>();
hobbySet2.add("H2");
hobbySet2.add("H4");
Set<String> hobbySet3 = new HashSet<String>();
hobbySet3.add("H3");
Set<String> hobbySet4 = new HashSet<String>();
hobbySet4.add("H4");
persons.add(new Person("P1", hobbySet1));
persons.add(new Person("P2", hobbySet2));
persons.add(new Person("P3", hobbySet3));
persons.add(new Person("P4", hobbySet4));
Set<String> searchHobby = new HashSet<String>();
searchHobby.add("H1");
searchHobby.add("H3");
I want to filter hobbies from List persons based on searchHobby, so that only those persons who have their hobbies specified in searchHobby will be retained.
I know how to achieve this using a simple for loop.
List<Person> result = new ArrayList<Person>();
for (String sHobby : searchHobby) {
for (Person p : persons) {
Set<String> pHobbySet = p.getHobbies();
for (String pHobby : pHobbySet) {
if (pHobby.equalsIgnoreCase(sHobby)) {
Optional<Person> wasAdded = result.stream().filter(s->s.getName()==p.getName()).findAny();
if(wasAdded.isEmpty()) {
result.add(p);
break;
}
}
}
}
}
I'm looking for a java Stream Filter solution.
P.S. Person
public class Person {
String name;
Set<String> hobbies;
public Person(String name, Set<String> hobbies) {
this.name = name;
this.hobbies = hobbies;
}
public String getName(){
return name;
}
public Set<String> getHobbies(){
return hobbies;
}
}
Upvotes: 0
Views: 1180
Reputation: 6300
You could use containsAll method to find a person who's hobbies contain all searchHobby
:
List<Person> collect = persons.stream()
.filter(person -> person.getHobbies().containsAll(searchHobby))
.collect(Collectors.toList());
Or if you need to find persons where at least one person's hobby is in searchHobbies
then you could use removeAll:
List<Person> collect = persons.stream()
.filter(person -> new HashSet<>(person.getHobbies()).removeAll(searchHobby))
.collect(Collectors.toList());
Upvotes: 1
Reputation: 66
anyMatch will return true if some hobby matches.
List<Person> personsWithHobby = persons.stream()
.filter(person -> person.getHobbies().stream()
.anyMatch(searchHobby::contains))
.collect(Collectors.toList());
Upvotes: 5
Reputation: 4140
You can try:
persons.stream()
.filter(p -> p.getHobbies().stream()
.filter(searchHobby::contains)
.findAny().isPresent()
)
.collect(Collectors.toList());
Upvotes: 1
Reputation: 109597
The for loops do handle the conditions a bit awkward, suboptimal. A simple solution would be:
List<Person> result = persons.stream()
.filter(p -> hasHobby(p.getHobbies(), searchHobby))
.collect(Collectors.toList());
boolean hasHobby(Set<String> personHobbies, Set<String> searchHobbies) {
Set<String> pH = personHobbiers.stream()
.map(String::toLowerCase).collect(Collectors.toSet());
Set<String> sH = searchHobbiers.stream()
.map(String::toLowerCase).collect(Collectors.toSet());
return !pH.retainAll(sh).isEmpty();
}
This is suboptimal too. Especially it is a pitty that one needs an ignore-case.
And simply taking an intersection is hard. However with a couple of hobbies this should not be hard. One could lowercase searchHobbies
at the start.
Upvotes: 0