Est-il possible de fusionner des itérateurs en Java? J'ai deux itérateurs et je veux les combiner/les fusionner afin de pouvoir parcourir leurs éléments en une seule fois (dans la même boucle) plutôt qu'en deux étapes. Est-ce possible?
Notez que le nombre d'éléments dans les deux listes peut être différent, donc une boucle sur les deux listes n'est pas la solution.
Iterator<User> pUsers = userService.getPrimaryUsersInGroup(group.getId());
Iterator<User> sUsers = userService.getSecondaryUsersInGroup(group.getId());
while(pUsers.hasNext()) {
User user = pUsers.next();
.....
}
while(sUsers.hasNext()) {
User user = sUsers.next();
.....
}
Guava (anciennement Google Collections) a Iterators.concat .
Les Apache Commons Collection ont également plusieurs classes pour manipuler les itérateurs, comme le IteratorChain , qui encapsule un certain nombre d'itérateurs.
Vous pouvez créer votre propre implémentation de l'interface Iterator
qui itère sur les itérateurs:
public class IteratorOfIterators implements Iterator {
private final List<Iterator> iterators;
public IteratorOfIterators(List<Iterator> iterators) {
this.iterators = iterators;
}
public IteratorOfIterators(Iterator... iterators) {
this.iterators = Arrays.asList(iterators);
}
public boolean hasNext() { /* implementation */ }
public Object next() { /* implementation */ }
public void remove() { /* implementation */ }
}
(Je n'ai pas ajouté de génériques à l'itérateur pour plus de brièveté.) L'implémentation n'est pas trop difficile, mais n'est pas la plus triviale, vous devez gardez une trace sur laquelle Iterator
vous êtes en train d'itérer et en appelant next()
vous devrez parcourir autant que possible les itérateurs jusqu'à ce que vous trouviez une hasNext()
qui renvoie true
, ou vous pouvez toucher la fin du dernier itérateur.
Je ne suis au courant d'aucune implémentation qui existe déjà pour cela.
Mise à jour:
J'ai voté positivement Andrew Duffy's réponse - pas besoin de réinventer la roue. Je dois vraiment approfondir la goyave.
J'ai ajouté un autre constructeur pour un nombre variable d'arguments - sortant presque du sujet, car la façon dont la classe est construite ici n'a pas vraiment d'intérêt, juste le concept de son fonctionnement.
Je n'ai pas écrit Java code depuis un moment, et cela m'a curieux de savoir si je l'ai toujours "compris").
Premier essai:
import Java.util.Iterator;
import Java.util.Arrays; /* For sample code */
public class IteratorIterator<T> implements Iterator<T> {
private final Iterator<T> is[];
private int current;
public IteratorIterator(Iterator<T>... iterators)
{
is = iterators;
current = 0;
}
public boolean hasNext() {
while ( current < is.length && !is[current].hasNext() )
current++;
return current < is.length;
}
public T next() {
while ( current < is.length && !is[current].hasNext() )
current++;
return is[current].next();
}
public void remove() { /* not implemented */ }
/* Sample use */
public static void main(String... args)
{
Iterator<Integer> a = Arrays.asList(1,2,3,4).iterator();
Iterator<Integer> b = Arrays.asList(10,11,12).iterator();
Iterator<Integer> c = Arrays.asList(99, 98, 97).iterator();
Iterator<Integer> ii = new IteratorIterator<Integer>(a,b,c);
while ( ii.hasNext() )
System.out.println(ii.next());
}
}
Vous pourriez bien sûr utiliser plus de classes Collection plutôt qu'un pur tableau + compteur d'index, mais cela semble en fait un peu plus propre que l'alternative. Ou suis-je simplement biaisé d'écrire principalement C ces jours-ci?
Quoi qu'il en soit, voilà. La réponse à votre question est "oui, probablement".
public class IteratorJoin<T> implements Iterator<T> {
private final Iterator<T> first, next;
public IteratorJoin(Iterator<T> first, Iterator<T> next) {
this.first = first;
this.next = next;
}
@Override
public boolean hasNext() {
return first.hasNext() || next.hasNext();
}
@Override
public T next() {
if (first.hasNext())
return first.next();
return next.next();
}
}
déplacez votre boucle vers une méthode et passez l'itérateur à la méthode.
void methodX(Iterator x) {
while (x.hasNext()) {
....
}
}
un itérateur provient d'une collection ou d'un ensemble.
pourquoi ne pas utiliser la méthode déjà disponibleCollection.addAll(Collection c);
puis créez votre itérateur à partir du dernier objet.
de cette façon, votre itérateur itérera tout le contenu des deux collections.
Vous pouvez utiliser ma version d'un itérateur extensible. Il utilise une file d'attente d'itérateurs à double extrémité, ce qui me semble logique:
import Java.util.Deque;
import Java.util.Iterator;
import Java.util.concurrent.ConcurrentLinkedDeque;
public class ExtendableIterator<T> implements Iterator<T> {
public Deque<Iterator<T>> its = new ConcurrentLinkedDeque<Iterator<T>>();
public ExtendableIterator() {
}
public ExtendableIterator(Iterator<T> it) {
this();
this.extend(it);
}
@Override
public boolean hasNext() {
// this is true since we never hold empty iterators
return !its.isEmpty() && its.peekLast().hasNext();
}
@Override
public T next() {
T next = its.peekFirst().next();
if (!its.peekFirst().hasNext()) {
its.removeFirst();
}
return next;
}
public void extend(Iterator<T> it) {
if (it.hasNext()) {
its.addLast(it);
}
}
}
À partir de Java 8 et versions ultérieures, cela peut être fait sans dépendances externes en utilisant Stream API . Cela permet également la concaténation de l'itérateur avec d'autres types de flux.
Streams.concat(StreamSupport.stream(<iter1>, false), StreamSupport.stream(<iter2>, false));
Je remanierais la conception originale de:
Iterator<User> pUsers = userService.getPrimaryUsersInGroup(group.getId());
Iterator<User> sUsers = userService.getSecondaryUsersInGroup(group.getId());
Pour quelque chose comme:
Iterator<User> users = userService.getUsersInGroup(group.getId(), User.PRIMARY, User.SECONDARY, ...);
Dans le Apache Commons Collections il y a public static <E> Iterator<E> org.Apache.commons.collections4.IteratorUtils.chainedIterator(Collection<Iterator<? extends E>> iterators)
qui dit
Obtient un itérateur qui itère à travers une collection d'itérateurs l'un après l'autre.
ce qui devrait être ce que vous voulez.
import Java.util.Arrays;
import Java.util.Iterator;
import org.Apache.commons.collections4.IteratorUtils;
//also works: import org.Apache.commons.collections.IteratorUtils;
class Scratch {
public static void main( String[] args ) {
final Iterator<String> combinedIterator = IteratorUtils.chainedIterator(
Arrays.asList( "a", "b", "c" ).iterator(),
Arrays.asList( "1", "2", "3" ).iterator()
);
while( combinedIterator.hasNext() ){
System.out.println( combinedIterator.next() );
}
// "abc123" will have been printed out
}
}
Vous pouvez essayer ConcatIterator
à partir de Cactoos :
Iterator<String> names = new ConcatIterator<>(
Arrays.asList("Sarah", "Mary").iterator(),
Arrays.asList("Jeff", "Johnny").iterator(),
);
Vérifiez également ConcatIterable
, qui concatène Iterable
s.
L'itérateur fusionné:
import static Java.util.Arrays.asList;
import Java.util.Iterator;
import Java.util.LinkedList;
import Java.util.List;
import Java.util.NoSuchElementException;
public class ConcatIterator<T> implements Iterator<T> {
private final List<Iterable<T>> iterables;
private Iterator<T> current;
@SafeVarargs
public ConcatIterator(final Iterable<T>... iterables) {
this.iterables = new LinkedList<>(asList(iterables));
}
@Override
public boolean hasNext() {
checkNext();
return current != null && current.hasNext();
}
@Override
public T next() {
checkNext();
if (current == null || !current.hasNext()) throw new NoSuchElementException();
return current.next();
}
@Override
public void remove() {
if (current == null) throw new IllegalStateException();
current.remove();
}
private void checkNext() {
while ((current == null || !current.hasNext()) && !iterables.isEmpty()) {
current = iterables.remove(0).iterator();
}
}
}
La méthode concat
pour créer un Iterable
:
@SafeVarargs
public static <T> Iterable<T> concat(final Iterable<T>... iterables) {
return () -> new ConcatIterator<>(iterables);
}
Test JUnit simple:
@Test
public void testConcat() throws Exception {
final Iterable<Integer> it1 = asList(1, 2, 3);
final Iterable<Integer> it2 = asList(4, 5);
int j = 1;
for (final int i : concat(it1, it2)) {
assertEquals(j, i);
j++;
}
}
chaque objet Iterator
possède son propre emplacement mémoire (adresse), vous ne pouvez donc pas simplement les "fusionner". sauf si vous étendez la classe iterator
et y écrivez votre propre implémentation.
Si vous traitez avec même nombre d'objets dans les deux itérateurs, une solution alternative serait de traiter deux itérateurs dans une boucle comme ceci:
while (iterator1.hasNext() && iterator2.hasNext()) {
// code
}