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.

Author: Kesh, 2016-01-11

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));
 6
Author: erickson, 2016-01-12 16:50:15

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.

 3
Author: Helder Pereira, 2016-01-12 21:25:07

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

comparingInt vient d'un static import de Comparator.

 2
Author: Gaël J, 2016-01-11 21:16:26