¿Cuál es la mejor manera de obtener el recuento / la longitud / el tamaño de un iterador?

96

¿Existe una forma rápida "computacionalmente" de obtener el recuento de un iterador?

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

... parece una pérdida de ciclos de CPU.

Zak
fuente
2
Un iterador no corresponde necesariamente a algo con un "recuento" ...
Oliver Charlesworth
Los iteradores son lo que son; para iterar al siguiente objeto de una colección (puede ser cualquier cosa como conjunto, matriz, etc.) ¿Por qué necesitan decir el tamaño cuando no les importa para qué están tratando de iterar? 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

Respuestas:

67

Si acaba de obtener el iterador, entonces eso es lo que tendrá que hacer: no sabe cuántos elementos le quedan para iterar, por lo que no puede consultarlo para obtener ese resultado. Hay métodos de utilidad que parecerán hacer esto (como Iterators.size()en Guava), pero en el fondo solo realizan aproximadamente la misma operación.

Sin embargo, muchos iteradores provienen de colecciones, que a menudo puede consultar por su tamaño. Y si se trata de una clase creada por el usuario para la que está obteniendo el iterador, puede buscar proporcionar un método size () en esa clase.

En resumen, en la situación en la que solo tiene el iterador, entonces no hay mejor manera, pero la mayoría de las veces tiene acceso a la colección u objeto subyacente desde el cual puede obtener el tamaño directamente.

Michael Berry
fuente
Tenga cuidado con el efecto secundario de Iterators.size(...)(mencionado en otros comentarios a continuación y en java-doc): "Devuelve el número de elementos que quedan en el iterador. El iterador quedará agotado: su método hasNext () devolverá falso". Eso significa que ya no puede usar el Iterador después. Lists.newArrayList(some_iterator);puede ayudar.
MichaelCkr
91

Usando la biblioteca de Guava :

int size = Iterators.size(iterator);

Internamente, solo itera sobre todos los elementos, por lo que es solo por conveniencia.

Andrejs
fuente
8
Esto es muy elegante. Solo recuerde que está consumiendo su iterador (es decir, el iterador estará vacío después)
lolski
1
Esto no es "computacionalmente rápido", es un método conveniente que tiene el efecto secundario no deseado de consumir el iterador.
Zak
¿Puede explicar cómo funciona esto? @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

Su código le dará una excepción cuando llegue al final del iterador. Podrías hacerlo:

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

Si tuviera acceso a la colección subyacente, podría llamar coll.size()...

EDITAR OK ha modificado ...

Assylias
fuente
¿Qué tan eficiente es esto? ¿Qué pasa si el iterador es como un millón de valores?
Micro
4
@Micro técnicamente, un iterador podría ser infinito, en cuyo caso el ciclo continuará para siempre.
Assylias
11

Siempre tendrás que iterar. Sin embargo, puede usar Java 8, 9 para hacer el conteo sin hacer un bucle explícito:

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

Aquí hay una prueba:

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);
}

Esto imprime:

5

Es bastante interesante que puede paralelizar la operación de conteo aquí cambiando la parallelbandera en esta llamada:

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

Usando la biblioteca de Guava , otra opción es convertir el archivoIterable a List.

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

Úselo si también necesita acceder a los elementos del iterador después de obtener su tamaño. Al usarlo Iterators.size(), ya no puede acceder a los elementos iterados.

tashuhka
fuente
2
@LoveToCode Menos eficiente que el ejemplo de la pregunta original
Invierno
2
Claro, crear un nuevo objeto con todos los elementos es más lento que simplemente iterar y descartar. En mi humilde opinión, esta solución es una línea que mejora la legibilidad del código. Lo uso mucho para colecciones con pocos elementos (hasta 1000) o cuando la velocidad no es un problema.
Tashuhka
7

Si todo lo que tiene es el iterador, entonces no, no hay una forma "mejor". Si el iterador proviene de una colección, podría usarlo por tamaño.

Tenga en cuenta que Iterator es solo una interfaz para atravesar valores distintos, muy bien tendría un código como este

    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");
        }
    };

o

    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ö
fuente
4

No hay forma más eficiente, si todo lo que tiene es el iterador. Y si el iterador solo se puede usar una vez, entonces obtener el recuento antes de obtener el contenido del iterador es ... problemático.

La solución es cambiar su aplicación para que no necesite el recuento o obtener el recuento por algún otro medio. (Por ejemplo, pase a en Collectionlugar de Iterator...)

Stephen C
fuente
0

para Java 8 puedes usar

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

El objeto iterador contiene el mismo número de elementos que su colección.

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

Pero en lugar de obtener el tamaño del iterador e iterar a través del índice 0 hasta ese tamaño, es mejor iterar a través del método next () del iterador.

Chandra Sekhar
fuente
¿Y si no tenemos a, pero solo i?
Tvde1