Quelle est la meilleure façon d'obtenir le nombre / la longueur / la taille d'un itérateur?

96

Existe-t-il un moyen rapide "de calcul" pour obtenir le nombre d'itérateurs?

int i = 0;
for ( ; some_iterator.hasNext() ; ++i ) some_iterator.next();

... semble être un gaspillage de cycles CPU.

Zak
la source
2
Un itérateur ne correspond pas nécessairement à quelque chose avec un "décompte" ...
Oliver Charlesworth
Les itérateurs sont ce qu'ils sont; pour itérer vers l'objet suivant d'une collection (cela peut être quelque chose comme set, array, etc.) Pourquoi ont-ils besoin de dire la taille quand ils ne se soucient pas de ce qu'ils essaient d'itérer? to provide an implementation-independent method for access, in which the user does not need to know whether the underlying implementation is some form of array or of linked list, and allows the user go through the collection without explicit indexing. penguin.ewu.edu/~trolfe/LinkedSort/Iterator.html
ecle

Réponses:

67

Si vous avez juste l'itérateur, c'est ce que vous devrez faire - il ne sait pas combien d'éléments il reste à parcourir, vous ne pouvez donc pas l'interroger pour ce résultat. Il existe des méthodes utilitaires qui semblent faire cela (comme Iterators.size()dans Guava), mais en dessous, elles effectuent à peu près la même opération.

Cependant, de nombreux itérateurs proviennent de collections, que vous pouvez souvent interroger pour leur taille. Et s'il s'agit d'une classe créée par l'utilisateur pour laquelle vous obtenez l'itérateur, vous pouvez chercher à fournir une méthode size () sur cette classe.

En bref, dans la situation où vous n'avez que l'itérateur, il n'y a pas de meilleur moyen, mais le plus souvent, vous avez accès à la collection ou à l'objet sous-jacent à partir duquel vous pourrez peut-être obtenir directement la taille.

Michael Berry
la source
Attention à l'effet secondaire de Iterators.size(...)(mentionné dans d'autres commentaires ci-dessous et dans java-doc): "Renvoie le nombre d'éléments restants dans l'itérateur. L'itérateur sera laissé épuisé: sa méthode hasNext () retournera false." Cela signifie que vous ne pouvez plus utiliser l'itérateur par la suite. Lists.newArrayList(some_iterator);peut aider.
MichaelCkr
91

Utilisation de la bibliothèque Guava :

int size = Iterators.size(iterator);

En interne, il itère simplement sur tous les éléments, donc c'est juste pour plus de commodité.

Andrejs
la source
8
C'est très élégant. N'oubliez pas que vous consommez votre itérateur (c'est-à-dire que l'itérateur sera vide par la suite)
lolski
1
Ce n'est pas "rapide en calcul", c'est une méthode pratique qui a pour effet secondaire indésirable de consommer l'itérateur.
Zak
Pouvez-vous expliquer comment cela fonctionne? @Andrejs List <Tuple2 <String, Integer >> wordCountsWithGroupByKey = wordsPairRdd.groupByKey () .mapValues ​​(intIterable -> Iterables.size (intIterable)). Collect (); System.out.println ("wordCountsWithGroupByKey:" + wordCountsWithGroupByKey); "Iterables.size (intIterable)?
Aditya Verma
15

Votre code vous donnera une exception lorsque vous atteindrez la fin de l'itérateur. Vous pourriez faire:

int i = 0;
while(iterator.hasNext()) {
    i++;
    iterator.next();
}

Si vous aviez accès à la collection sous-jacente, vous pourriez appeler coll.size()...

EDIT OK vous avez modifié ...

assylies
la source
à quel point est-ce efficace? et si l'itérateur est comme un million de valeurs?
Micro
4
@Micro techniquement, un itérateur pourrait être infini - auquel cas la boucle durera indéfiniment.
assylias
12

Vous devrez toujours itérer. Pourtant, vous pouvez utiliser Java 8, 9 pour faire le comptage sans boucler explicitement:

Iterable<Integer> newIterable = () -> iter;
long count = StreamSupport.stream(newIterable.spliterator(), false).count();

Voici un test:

public static void main(String[] args) throws IOException {
    Iterator<Integer> iter = Arrays.asList(1, 2, 3, 4, 5).iterator();
    Iterable<Integer> newIterable = () -> iter;
    long count = StreamSupport.stream(newIterable.spliterator(), false).count();
    System.out.println(count);
}

Cela imprime:

5

Assez intéressant, vous pouvez paralléliser l'opération de comptage ici en changeant le paralleldrapeau sur cet appel:

long count = StreamSupport.stream(newIterable.spliterator(), *true*).count();
gil.fernandes
la source
8

En utilisant la bibliothèque Goyave , une autre option est de convertir l' Iterableun List.

List list = Lists.newArrayList(some_iterator);
int count = list.size();

Utilisez ceci si vous devez également accéder aux éléments de l'itérateur après avoir obtenu sa taille. En utilisant, Iterators.size()vous ne pouvez plus accéder aux éléments itérés.

tashuhka
la source
2
@LoveToCode Moins efficace que l'exemple sur la question d'origine
Hiver
2
Bien sûr, la création d'un nouvel objet avec tous les éléments est plus lente que la simple itération et la suppression. À mon humble avis, cette solution est une ligne unique qui améliore la lisibilité du code. Je l'utilise beaucoup pour les collections avec peu d'éléments (jusqu'à 1000) ou lorsque la vitesse n'est pas un problème.
tashuhka
7

Si tout ce que vous avez est l'itérateur, alors non, il n'y a pas de «meilleur» moyen. Si l'itérateur provient d'une collection, vous pourriez aussi bien la taille.

Gardez à l'esprit qu'Iterator est juste une interface pour parcourir des valeurs distinctes, vous auriez très bien un code comme celui-ci

    new Iterator<Long>() {
        final Random r = new Random();
        @Override
        public boolean hasNext() {
            return true;
        }

        @Override
        public Long next() {
            return r.nextLong();
        }

        @Override
        public void remove() {
            throw new IllegalArgumentException("Not implemented");
        }
    };

ou

    new Iterator<BigInteger>() {
        BigInteger next = BigInteger.ZERO;

        @Override
        public boolean hasNext() {
            return true;
        }

        @Override
        public BigInteger next() {
            BigInteger current = next;
            next = next.add(BigInteger.ONE);
            return current;
        }

        @Override
        public void remove() {
            throw new IllegalArgumentException("Not implemented");
        }
    }; 
Roger Lindsjö
la source
4

Il n'y a pas de moyen plus efficace, si vous ne disposez que de l'itérateur. Et si l'itérateur ne peut être utilisé qu'une seule fois, alors obtenir le décompte avant d'obtenir le contenu de l'itérateur est ... problématique.

La solution consiste soit à modifier votre application pour qu'elle n'ait pas besoin du décompte, soit à obtenir le décompte par d'autres moyens. (Par exemple, passez un Collectionplutôt que Iterator...)

Stephen C
la source
0

pour Java 8, vous pouvez utiliser,

public static int getIteratorSize(Iterator iterator){
        AtomicInteger count = new AtomicInteger(0);
        iterator.forEachRemaining(element -> {
            count.incrementAndGet();
        });
        return count.get();
    }
robbie70
la source
-5

L'objet iterator contient le même nombre d'éléments que votre collection contenait.

List<E> a =...;
Iterator<E> i = a.iterator();
int size = a.size();//Because iterators size is equal to list a's size.

Mais au lieu d'obtenir la taille de l'itérateur et d'itérer via l'index 0 à cette taille, il est préférable d'itérer via la méthode next () de l'itérateur.

Chandra Sekhar
la source
Et si nous n'en avons pas a, mais seulement i?
Tvde1