Java 8 Filter Set Based on Another Set

Using Java 8 new constructs such as threads, for example, is there a way to filter Set based on the order in another collection?

 Set<Person> persons = new HashSet<>(); persons.add(new Person("A", 23)); persons.add(new Person("B", 27)); persons.add(new Person("C", 20)); List<String> names = new ArrayList<>(); names.add("B"); names.add("A"); 

I want to filter elements from the persons set based on names , so that only those who have their own names specified in names will be saved, but in the order they appear in names .

So i want

 Set<Person> filteredPersons = ...; 

where the 1st element is Person("B", 27) , and the second element is Person("A", 23) .

If I do the following,

 Set<Person> filteredPersons = new HashSet<>(persons); filteredPersons = filteredPersons.stream().filter(p -> names.contains(p.getName())).collect(Collectors.toSet()); 

the order is not guaranteed to be the same as in names , if I'm not mistaken.

I know how to achieve this using a simple loop; I'm just looking for a way to use java 8.

Thanks for watching!

EDIT:

A for loop that achieves the same result:

 Set<Person> filteredPersons = new LinkedHashSet<>(); for (String name : names) { for (Person person : persons) { if (person.getName().equalsIgnoreCase(name)) { filteredPersons.add(person); break; } } } 

The implementation of LinkedHashSet ensures that order is maintained.

+5
source share
3 answers
 final Set<Person> persons = ... Set<Person> filteredPersons = names.stream() .flatMap(n -> persons.stream().filter(p -> n.equals(p.getName())) ) .collect(Collectors.toCollection(LinkedHashSet::new)); 

Collect flows of people created by filtering them by each name. This is fast for situations like the example above, but will scale linearly with the number of people, such as O (N * P).

For large collections of faces and names, creating an index that can be used to search for a person by name will be faster overall, scaling as O (N + P):

 Map<String, Person> index = persons.stream() .collect(Collectors.toMap(Person::getName, Function.identity())); Set<Person> filteredPersons = names.stream() .map(index::get) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); 
+6
source

I am open to changing the implementation of the set, for example, for LinkedHashSet, as shown in the example above, to achieve the final goal.

If you are completely open to changing the data structure used to store persons , you should probably consider using Map , as this will greatly improve the efficiency of your algorithm.

 Map<String, Person> persons = new HashMap<>(); persons.put("A", new Person("A", 23)); persons.put("B", new Person("B", 27)); persons.put("C", new Person("C", 20)); List<String> names = new ArrayList<>(); names.add("B"); names.add("A"); List<Person> filteredPersons = names.stream() .map(persons::get) .filter(Objects::nonNull) .collect(Collectors.toList()); 

If the case of letters can differ in persons and names , you can do .toLowerCase() in the Map keys.

+3
source

You can use something like the following (not tested):

 .sorted((p1, p2) -> Integer.compare(names.indexOf(p1.getName()), names.indexOf(p2.getName()))) 

And, as mentioned above, build instead of Set .


As mentioned in Alexis' comment, you can also write this more briefly:

 .sorted(comparingInt(p -> names.indexOf(p.getName()))) 

Where comparingInt comes from static import from Comparator .

+2
source

Source: https://habr.com/ru/post/1240374/


All Articles