Obtenir la taille d'un Iterable en Java

88

J'ai besoin de comprendre le nombre d'éléments dans un Iterableen Java. Je sais que je peux le faire:

Iterable values = ...
it = values.iterator();
while (it.hasNext()) {
  it.next();
  sum++;
}

Je pourrais aussi faire quelque chose comme ça, car je n'ai plus besoin des objets dans Iterable:

it = values.iterator();
while (it.hasNext()) {
  it.remove();
  sum++;
}

Un benchmark à petite échelle n'a pas montré beaucoup de différence de performance, des commentaires ou d'autres idées pour ce problème?

js84
la source
Pourquoi? L'un ou l'autre est une très mauvaise idée, car les deux sont O (N). Vous devriez essayer d'éviter d'avoir à itérer la collection deux fois.
Marquis of Lorne
3
Votre deuxième ne devrait même pas fonctionner. Vous ne pouvez pas appeler remove()sans appeler next()au préalable.
Louis Wasserman

Réponses:

123

TL; DR: Utilisez la méthode utilitaire Iterables.size(Iterable)de la grande bibliothèque Guava .

De vos deux extraits de code, vous devez utiliser le premier, car le second supprimera tous les éléments values, il sera donc vide par la suite. Changer une structure de données pour une requête simple telle que sa taille est très inattendu.

Pour les performances, cela dépend de votre structure de données. Si c'est par exemple en fait un ArrayList, supprimer des éléments depuis le début (ce que fait votre deuxième méthode) est très lent (le calcul de la taille devient O (n * n) au lieu de O (n) comme il se doit).

En général, s'il y a une chance que ce valuessoit réellement un Collectionet pas seulement un Iterable, vérifiez ceci et appelez size()au cas où:

if (values instanceof Collection<?>) {
  return ((Collection<?>)values).size();
}
// use Iterator here...

L'appel à size()sera généralement beaucoup plus rapide que de compter le nombre d'éléments, et cette astuce est exactement ce que Iterables.size(Iterable)de goyave fait pour vous.

Philipp Wendler
la source
Il dit qu'il n'est pas intéressé par les éléments et ne se soucie pas de savoir s'ils sont supprimés.
aioobe
Petite précision: il supprimera les éléments devalues
Miquel
1
Mais d'autres parties du code peuvent toujours utiliser le même Iterable. Et sinon maintenant, peut-être dans le futur.
Philipp Wendler
Je suggère de rendre la réponse de Google Guava plus visible. Il n'y a aucune raison de forcer les gens à réécrire ce code, même si c'est trivial.
Stephen Harrison
8
Si vous utilisez Java 8, créez un Stream et comptez-y l'élément comme suit: Stream.of (myIterable) .count ()
FrankBr
41

Si vous travaillez avec java 8, vous pouvez utiliser:

Iterable values = ...
long size = values.spliterator().getExactSizeIfKnown();

cela ne fonctionnera que si la source itérable a une taille déterminée. La plupart des Spliterators for Collections le feront, mais vous pouvez avoir des problèmes si cela vient d'un HashSetou ResultSetpar exemple.

Vous pouvez consulter le javadoc ici.

Si Java 8 n'est pas une option , ou si vous ne savez pas d'où vient l'itérable, vous pouvez utiliser la même approche que la goyave:

  if (iterable instanceof Collection) {
        return ((Collection<?>) iterable).size();
    } else {
        int count = 0;
        Iterator iterator = iterable.iterator();
        while(iterator.hasNext()) {
            iterator.next();
            count++;
        }
        return count;
    }
ArnaudR
la source
1
Quelqu'un, donne une médaille à ce gars.
Pramesh Bajracharya
Cela ne fonctionne pas pour moi pour Sort . Answer by New Bee fonctionne pour moi.
Sabir Khan le
18

C'est peut-être un peu tard, mais cela peut aider quelqu'un. Je rencontre un problème similaire Iterabledans ma base de code et la solution consistait à utiliser for eachsans appeler explicitement values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}
pilote
la source
2
C'est, pour moi, une approche intuitive, que je peux apprécier. Je viens de l'utiliser il y a quelques minutes. Merci.
Matt Cremeens le
2
Oui, c'est intuitif, mais malheureusement, vous devez supprimer un avertissement non utilisé pour la valeur ...
Nils-o-mat
Merci pour cette solution, elle renvoie en effet la taille de l'objet. Le seul inconvénient est que si vous souhaitez effectuer une nouvelle itération plus tard à travers le même objet, vous devez d'abord le réinitialiser.
Zsolti
7

Vous pouvez convertir votre itérable dans une liste, puis utiliser .size () dessus.

Lists.newArrayList(iterable).size();

Par souci de clarté, la méthode ci-dessus nécessitera l'importation suivante:

import com.google.common.collect.Lists;
collations
la source
2
Les listes sont une classe de goyave
Paul Jackson
6

À proprement parler, Iterable n'a pas de taille. Pensez à la structure de données comme à un cycle.

Et pensez à suivre l'instance Iterable, aucune taille:

    new Iterable(){

        @Override public Iterator iterator() {
            return new Iterator(){

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

                @Override
                public Object next() {
                    return fetchDataFromExternalSystem();
                }};
        }};
卢 声 远 Shengyuan Lu
la source
2

J'irais pour it.next()la simple raison qui next()est garantie d'être implémentée, alors que remove()c'est une opération facultative.

E next()

Renvoie l'élément suivant de l'itération.

void remove()

Supprime de la collection sous-jacente le dernier élément retourné par l'itérateur (opération facultative) .

aioobe
la source
Bien que cette réponse soit techniquement correcte, elle est assez trompeuse. L'appel removea déjà été noté comme une mauvaise façon de compter les éléments d'un Iterator, donc peu importe si la chose que nous n'allons pas faire est implémentée ou non.
Stephen Harrison
1
Quelle partie exactement de la réponse est trompeuse? Et dans les circonstances où remove est mis en œuvre, pourquoi serait-il erroné de l'utiliser? Btw, les votes négatifs ne sont généralement pas utilisés pour de mauvaises réponses ou des réponses qui donnent de mauvais conseils. Je ne vois pas comment cette réponse se qualifie pour tout cela.
aioobe
2

java 8 et supérieur

StreamSupport.stream(data.spliterator(), false).count();
Nouvelle abeille
la source
0

Quant à moi, ce ne sont que des méthodes différentes. Le premier laisse l'objet sur lequel vous itérez inchangé, tandis que les secondes le laisse vide. La question est de savoir que voulez-vous faire. La complexité de la suppression est basée sur l'implémentation de votre objet itérable. Si vous utilisez des collections - obtenez simplement la taille proposée par Kazekage Gaara - c'est généralement la meilleure approche en termes de performances.

Mark Bramnik
la source
-2

Pourquoi n'utilisez-vous pas simplement la size()méthode sur votre Collectionpour obtenir le nombre d'éléments?

Iterator est juste destiné à itérer, rien d'autre.

Kazekage Gaara
la source
4
Qui dit qu'il utilise une collection? :-)
aioobe
Vous utilisez un itérateur, qui prend en charge la suppression d'éléments. Si vous choisissez la taille avant d'entrer dans la boucle d'itérateur, vous devez faire attention à vos suppressions et mettre à jour la valeur en conséquence.
Miquel
1
Un exemple de la raison pour laquelle il n'a peut-être pas de collection à examiner pour la taille: il pourrait appeler une méthode API tierce qui ne renvoie qu'un Iterable.
SteveT
2
Cette réponse confond Iteratoret Iterable. Voir la réponse votée pour la bonne manière.
Stephen Harrison
-4

Au lieu d'utiliser des boucles et de compter chaque élément ou d'utiliser une bibliothèque tierce, nous pouvons simplement typer l'itérable dans ArrayList et obtenir sa taille.

((ArrayList) iterable).size();
fatimasajjad
la source
3
Tous les itérables ne peuvent pas être castés dans ArrayList tho
Alexander Mills