web-dev-qa-db-fra.com

Est-il possible de fusionner des itérateurs en Java?

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();
  .....
}
45
Jahanzeb Farooq

Guava (anciennement Google Collections) a Iterators.concat .

50
Andrew Duffy

Les Apache Commons Collection ont également plusieurs classes pour manipuler les itérateurs, comme le IteratorChain , qui encapsule un certain nombre d'itérateurs.

20
Ither

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.

16
Noel M

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".

13
Christoffer
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();
    }
}
4
clelson

déplacez votre boucle vers une méthode et passez l'itérateur à la méthode.

void methodX(Iterator x) {
    while (x.hasNext()) {
        ....
    }
}
3
mhshams

un itérateur provient d'une collection ou d'un ensemble.
pourquoi ne pas utiliser la méthode déjà disponible
Collection.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.

3
p01ntbl4nk

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);
        }
    }
}
2
Reut Sharabani

À 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));
2
Taras Alenin

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, ...);
1

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
    }
}
1
Sled

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 Iterables.

1
yegor256

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++;
    }
}
1
benez

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
    }
0
Youssef