Obtener el tamaño de un iterable en Java

88

Necesito averiguar la cantidad de elementos Iterableen Java. Sé que puedo hacerlo:

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

También podría hacer algo como esto, porque ya no necesito los objetos en el Iterable:

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

Un punto de referencia a pequeña escala no mostró mucha diferencia de rendimiento, ¿algún comentario u otra idea para este problema?

js84
fuente
¿Por qué? Cualquiera de los dos es una muy mala idea, ya que ambos son O (N). Debe intentar evitar tener que iterar la colección dos veces.
Marqués de Lorne
3
Tu segundo ni siquiera debería funcionar. No puede llamar remove()sin llamar de next()antemano.
Louis Wasserman

Respuestas:

123

TL; DR: Utilice el método Iterables.size(Iterable)de utilidad de la gran biblioteca de Guava .

De sus dos fragmentos de código, debe usar el primero, porque el segundo eliminará todos los elementos de values, por lo que estará vacío después. Cambiar una estructura de datos para una consulta simple como su tamaño es muy inesperado.

Para el rendimiento, esto depende de su estructura de datos. Si es, por ejemplo, de hecho an ArrayList, eliminar elementos desde el principio (lo que está haciendo su segundo método) es muy lento (calcular el tamaño se convierte en O (n * n) en lugar de O (n) como debería ser).

En general, si existe la posibilidad de que en valuesrealidad sea un Collectiony no solo un Iterable, verifique esto y llame size()en caso de que:

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

La llamada a la size()voluntad general será mucho más rápido que contar el número de elementos, y este truco es exactamente lo que Iterables.size(Iterable)de guayaba hace por usted.

Philipp Wendler
fuente
Dice que no está interesado en los elementos y que no le importa si se eliminan.
aioobe
Pequeña aclaración: quitará los elementos devalues
Miquel
1
Pero es posible que otras partes del código sigan usando el mismo Iterable. Y si no ahora, quizás en el futuro.
Philipp Wendler
Sugiero hacer que la respuesta de Google Guava sea más prominente. No hay razón para hacer que la gente vuelva a escribir este código, aunque es trivial.
Stephen Harrison
8
Si usa Java 8, cree un elemento Stream y cuente en él como: Stream.of (myIterable) .count ()
FrankBr
41

Si está trabajando con java 8, puede usar:

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

solo funcionará si la fuente iterable tiene un tamaño determinado. La mayoría de los divisores para colecciones lo harán, pero es posible que tenga problemas si proviene de un archivo HashSeto, ResultSetpor ejemplo.

Puedes consultar el javadoc aquí.

Si Java 8 no es una opción , o si no sabe de dónde viene el iterable, puede usar el mismo enfoque que guava:

  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
fuente
1
Que alguien le dé una medalla a este tipo.
Pramesh Bajracharya
No me funciona para Sort . Respuesta de New Bee funciona para mí.
Sabir Khan
18

Quizás sea un poco tarde, pero puede ayudar a alguien. Me encontré con un problema similar Iterableen mi base de código y la solución fue usar for eachsin llamar explícitamente values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}
piloto
fuente
2
Este es, para mí, un enfoque intuitivo, que puedo apreciar. Lo usé hace unos minutos. Gracias.
Matt Cremeens
2
Sí, es intuitivo, pero lamentablemente tienes que suprimir una advertencia no utilizada por valor ...
Nils-o-mat
Gracias por esta solución, de hecho devuelve el tamaño del objeto. El único inconveniente es que si desea iterar nuevamente más tarde a través del mismo objeto, primero debe restablecerlo de alguna manera.
Zsolti
7

Puede convertir su iterable a una lista y luego usar .size () en él.

Lists.newArrayList(iterable).size();

En aras de la claridad, el método anterior requerirá la siguiente importación:

import com.google.common.collect.Lists;
aperitivos
fuente
2
Las listas son una clase de guayaba
Paul Jackson
6

Estrictamente hablando, Iterable no tiene tamaño. Piense en la estructura de datos como un ciclo.

Y piense en la siguiente instancia Iterable, sin tamaño:

    new Iterable(){

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

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

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

Yo optaría por it.next()la sencilla razón de que next()se garantiza que se implementará, mientras que remove()es una operación opcional.

E next()

Devuelve el siguiente elemento de la iteración.

void remove()

Elimina de la colección subyacente el último elemento devuelto por el iterador (operación opcional) .

aioobe
fuente
Si bien esta respuesta es técnicamente correcta, es bastante engañosa. Las llamadas removeya se han señalado como la forma incorrecta de contar elementos de un Iterator, por lo que realmente no importa si lo que no vamos a hacer se implementa o no.
Stephen Harrison
1
¿Exactamente qué parte de la respuesta es engañosa? Y bajo las circunstancias en las que remove se implementa, ¿por qué sería incorrecto usarlo? Por cierto, los votos negativos se usan típicamente para respuestas incorrectas o respuestas que dan malos consejos. No veo cómo esta respuesta califica para nada de eso.
aioobe
2

java 8 y superior

StreamSupport.stream(data.spliterator(), false).count();
Nueva abeja
fuente
0

En cuanto a mí, estos son solo métodos diferentes. El primero deja el objeto en el que está iterando sin cambios, mientras que el segundo lo deja vacío. La pregunta es qué quieres hacer. La complejidad de la eliminación se basa en la implementación de su objeto iterable. Si está utilizando Colecciones, solo obtenga el tamaño propuesto por Kazekage Gaara, generalmente es el mejor enfoque en cuanto al rendimiento.

Mark Bramnik
fuente
-2

¿Por qué no usas simplemente el size()método en tu Collectionpara obtener la cantidad de elementos?

Iterator está destinado a iterar, nada más.

Kazekage Gaara
fuente
4
¿Quién dice que está usando una colección? :-)
aioobe
Está utilizando un iterador, que admite la eliminación de elementos. Si elige el tamaño antes de ingresar al ciclo del iterador, debe tener cuidado con sus eliminaciones y actualizar el valor en consecuencia.
Miquel
1
Un ejemplo de por qué es posible que no tenga una colección para ver el tamaño: podría estar llamando a un método de API de terceros que devuelve solo un Iterable.
SteveT
2
Esta respuesta confunde Iteratory Iterable. Vea la respuesta votada de la manera correcta.
Stephen Harrison
-4

En lugar de usar bucles y contar cada elemento o usar una biblioteca de terceros, simplemente podemos encasillar el iterable en ArrayList y obtener su tamaño.

((ArrayList) iterable).size();
fatimasajjad
fuente
3
Aunque no todos los iterables se pueden enviar a ArrayList
Alexander Mills