Je veux faire une fonction de filtre de liste de chaînes en utilisant un Iterable
et un prédicat pour sélectionner les chaînes à conserver, les autres doivent être supprimées de la liste, mais je ne le suis pas comprendre comment je fais la suppression.
{"a","b"}
Pour cette entrée:
{"a","","b",""}
J'attends
static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) { for (T s: it) { if (pred.test(s)==false) { // what to do here? } } return ...; }
4 Réponses :
Comme toute Collection
est Iterable
, ajoutez simplement les éléments qualifiés à une nouvelle collection et renvoyez-la plus tard:
static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) { return StreamSupport.stream(it.spliterator(), false) .filter(pred) .collect(Collectors.toList()); }
Quelques informations :
pred.test (s) == false
sera plutôt simplifiée en !pred.test(s)
L'ensemble du contenu de la méthode peut être raccourci en utilisant java-stream de cette manière:
static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) { Collection<T> collection = new ArrayList<>(); for (T s: it) { if (!pred.test(s)) { collection.add(s); } } return collection; }
C'est une solution, mais pas efficace. Mieux vaut implémenter un itera tor personnalisé qui est capable de sauter des éléments - c'est ce que fait l'implémentation de Guava.
Dans l'exercice, ils me disent que la fonction doit récupérer un itérable et non une collection
@GFAA Toutes les collections
s sont itérables
. Il assigne à Collection
afin de pouvoir y ajouter des éléments.
Donc, au lieu de collection et itérable, je peux récupérer une liste ou une arraylist?
@Michael Oui, il existe de nombreuses façons. Je préfère ne pas importer Guava uniquement pour ce cas d'utilisation. Cependant, c'est une bonne pratique «d'inspirer» avec une implémentation déjà existante :)
@GFAA: Vous pouvez faire Collection
ou List
ou même < code> ArrayList Iterable
car il n'a pas de méthode add
et est uniquement pour la lecture.
@Nikolas Oui, je sais mais je suis au début de java et je ne pense pas que mon professeur veuille que nous utilisions encore Collection ou Java-Stream, je comprends que le code doit être gardé le plus abstrait possible. TY pour la réponse, maintenant je l'ai pleinement compris.
@GFAA: C'est pourquoi j'ai proposé deux moyens: le premier pour répondre pleinement à vos besoins et le second si ce serait moi-même.
Il est exact que pred.test (s) == false
peut être simplifié en ! Pred.test (s)
, mais ni l'un ni l'autre n'est correct lorsque le prédicat est censé spécifier lequel éléments à conserver .
Un avantage majeur de Iterable / Iterator est qu'ils peuvent diffuser les données à partir d'une grande source de données, par exemple. Cette solution n'est pas optimale dans la mesure où elle nécessite le chargement en masse de toutes les données dans la collection. Construire un itérateur qui applique le prédicat à chaque élément lors de son accès serait de loin préférable.
Un Iterable
représente la capacité de fournir un Itérateur
sur demande. Ainsi, pour décorer un itérable existant avec une logique de filtrage, vous devez implémenter le Iterator
de décoration.
static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) { return () -> StreamSupport.stream(it.spliterator(), false) .filter(pred).iterator(); }
que vous pouvez tester via
List<String> original = new ArrayList<>(); Collections.addAll(original, "foo", "bar", "baz"); Iterable<String> filter = select(original, s -> s.startsWith("b")); System.out.println(String.join(", ", filter)); original.removeIf(s -> !s.endsWith("r")); System.out.println(String.join(", ", filter));
Le plus grand défi lors de l'implémentation d'un tel Itérateur
, est de fournir les deux méthodes hasNext
et next
avec le bon sémantique, sans aucune garantie quant à la manière dont l'appelant les invoquera, c'est à dire que vous ne pouvez pas supposer qu'il n'invoquera jamais hasNext ()
deux fois ni que next ()
sera toujours invoqué avec un hasNext()
.
La même logique peut être mise en œuvre beaucoup plus facilement en utilisant l'API Stream:
static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) { return () -> new Iterator<T>() { Iterator<T> sourceIterator = it.iterator(); T current; boolean hasCurrent; @Override public boolean hasNext() { while(!hasCurrent) { if(!sourceIterator.hasNext()) { return false; } T next = sourceIterator.next(); if(pred.test(next)) { current = next; hasCurrent = true; } } return true; } @Override public T next() { if(!hasNext()) throw new NoSuchElementException(); T next = current; current = null; hasCurrent = false; return next; } }; }
guava a résolu ce modèle compliqué en interne en utilisant un AbstractIterator
qui n'aurait qu'une seule méthode computeNext
IIRC; à peu près la même idée que tryAdvance
. Cela leur permet d'avoir Iterators :: filter
facilement implémentés.
Je pense que je préfère faire avancer l'itérateur source sur next ()
plutôt que dans hasNext ()
, puis stocker la valeur "next" ou null
si aucun n'est disponible. Cela devrait également réduire un peu la complexité.
@daniu qui n'aide pas avec le cas d'utilisation habituel de hasNext ()
étant appelé avant next ()
et devant donner la bonne réponse.
Je n'aurais peut-être pas expliqué correctement, voici ce que je voulais dire: stackoverflow.com/a/56399526/2837741
Je viens de réaliser que le comportement de votre solution (la mienne aussi) que je ne m'attendrais pas au comportement que si la collection sous-jacente est modifiée - comme dans votre cas de test -, cela entraîne une modification du code Iterable > retourné avant le changement.
@daniu c'est un comportement intentionnel; il décore un itérable ou en crée une vue filtrée. Tout comme le Iterables.filter
de Guava, comme mentionné dans ce commentaire fait. Si vous voulez un instantané plutôt qu'une vue, il n'y a aucun moyen de collecter les éléments, comme cette réponse .
Enveloppez d'abord votre Iterable
dans Stream
:
Java simple:
static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) { Stream<T> stream = stream(it.spliterator(), false); Predicate<T> negatedPred = pred.negate(); Stream<T> filteredStream = stream.filter(negatedPred); return filteredStream::iterator; }
static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) { return StreamSupport.stream(it.spliterator(), false).filter(pred.negate())::iterator; }
return stream::iterator;
Puis filtrez-le par votre Predicate
:
return () -> stream.iterator();
Et enfin retournez Iterable
comme lambda
:
... stream.filter(pred.negate()) ...
comme référence de méthode
StreamEx.of(it.iterator())
Exemple complet:
Streams.stream(it)
ou:
StreamSupport.stream(it.spliterator(), false)
La solution alternative à Holger que je voulais dire dans le commentaire ressemble à ceci:
static <T> Iterable<T> select(Iterable<T> toIterate, Predicate<T> pred) { return () -> new Iterator<T>() { Iterator<T> delegate = toIterate.iterator(); T next = findNextValid(); public boolean hasNext() { return next != null; } public T next() { if (next == null) throw new NoSuchElementException(); T result = next; next = findNextValid(); return result; } private T findNextValid() { T result = null; while (result == null && delegate.hasNext()) { T candidate = delegate.next(); if (pred.test(candidate)) { result = candidate; } } return result; } }; }
La différence est qu'il n'y a pas besoin d'un marqueur supplémentaire pour le hasCurrent
, et il fait avancer l ' Itérateur
avant que l'élément suivant ne soit réellement demandé. Vous pourriez cependant considérer ce dernier comme indésirable.
Avancer avant même que le premier élément n'ait été interrogé peut être un comportement surprenant. Mais ce n’est pas la raison de la différence concernant le champ hasCurrent
. Ma solution considère simplement la possibilité que la source contienne des éléments null
et que le prédicat les accepte. L'indicateur hasCurrent
est là pour gérer correctement ce cas. Si vous excluez null
, vous pouvez également remplacer le champ par un test null
dans ma solution.
Bête noire:
pred.test (s) == false
est un mauvais style. Utilisez! Pred.test (s)
Ty pour le tuyau!
BTW, la méthode que vous voulez existe déjà dans Guava:
Iterables.filter
. Vérifiez leur mise en œuvre.