Jeu de filtres Java 8 basé sur un autre jeu
En utilisant Java 8 new constructs, tels que streams par exemple, existe-t-il un moyen de filtrer un Set
en fonction de l'ordre dans une autre 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");
Je veux filtrer les éléments de l'ensemble persons
basée sur names
, de telle sorte que seules les personnes qui ont leur nom spécifié dans names
sera retenu, mais dans l'ordre où ils apparaissent dans names
.
Donc, je veux
Set<Person> filteredPersons = ...;
, Où le 1er élément est Person("B", 27)
et 2e élément est Person("A", 23)
.
Si je fais le suivant,
Set<Person> filteredPersons = new HashSet<>(persons);
filteredPersons = filteredPersons.stream().filter(p -> names.contains(p.getName())).collect(Collectors.toSet());
L'ordre n'est pas garanti pour être le même que dans names
, si je ne me trompe pas.
Je sais comment y parvenir en utilisant une simple boucle pour; Je cherche juste une façon java 8 de le faire.
Merci de regarder!
MODIFIER:
La boucle pour qui obtient le même résultat:
Set<Person> filteredPersons = new LinkedHashSet<>();
for (String name : names) {
for (Person person : persons) {
if (person.getName().equalsIgnoreCase(name)) {
filteredPersons.add(person);
break;
}
}
}
La mise en œuvre de LinkedHashSet
garantit le maintien de l'ordre.
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));
Recueillir les flux de personnes créées en les filtrant par chaque nom. C'est rapide pour des situations comme l'exemple fourni, mais évoluera linéairement avec le nombre de personnes, comme O(N*P).
Pour les plus grandes collections de personnes et de noms, la création d'un index qui peut être utilisé pour rechercher une personne par son nom sera plus rapide dans l'ensemble, mise à l'échelle comme 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));
Je suis ouvert à changer l'implémentation de l'ensemble, par exemple en a LinkedHashSet comme indiqué dans l'exemple ci-dessus pour atteindre l'objectif final.
Si vous êtes complètement ouvert pour changer la structure de données utilisée pour stocker le persons
, alors vous devriez probablement envisager d'utiliser un Map
, car cela améliorerait beaucoup l'efficacité de votre algorithme.
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());
Si la casse de la lettre peut être différente dans persons
et names
, vous pouvez faire un .toLowerCase()
dans les clés du Map
.
Vous pouvez utiliser quelque chose comme suivant (non testé):
.sorted((p1, p2) ->
Integer.compare(names.indexOf(p1.getName()),
names.indexOf(p2.getName())))
Et, comme mentionné ci-dessus, collectez à un List
au lieu d'un Set
.
Comme mentionné dans le commentaire d'Alexis, vous pouvez également écrire ceci de manière plus concise:
.sorted(comparingInt(p -> names.indexOf(p.getName())))
Où comparingInt
vient d'un static import
de Comparator
.