alex
alex

Reputation: 385

Java Stream Filter based on a Set-type

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

Answers (4)

Ruslan
Ruslan

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

Red_Shuhart
Red_Shuhart

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

lczapski
lczapski

Reputation: 4140

You can try:

persons.stream()
    .filter(p -> p.getHobbies().stream()
            .filter(searchHobby::contains)
            .findAny().isPresent()
    )
    .collect(Collectors.toList());

Upvotes: 1

Joop Eggen
Joop Eggen

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

Related Questions