Qual é a melhor maneira de obter a contagem / comprimento / tamanho de um iterador?

96

Existe uma maneira "computacionalmente" rápida de obter a contagem de um iterador?

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

... parece um desperdício de ciclos de CPU.

Zak
fonte
2
Um iterador não corresponde necessariamente a algo com uma "contagem" ...
Oliver Charlesworth
Os iteradores são o que são; para iterar para o próximo objeto de uma coleção (pode ser qualquer coisa como set, array, etc.) Por que eles precisam dizer o tamanho quando não se importam com o que estão tentando 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

Respostas:

67

Se você acabou de obter o iterador, isso é o que você terá que fazer - ele não sabe quantos itens faltam para iterar, então você não pode consultá-lo para aquele resultado. Existem métodos utilitários que parecem fazer isso (como Iterators.size()no Guava), mas por baixo eles estão apenas executando aproximadamente a mesma operação.

No entanto, muitos iteradores vêm de coleções, que geralmente você pode consultar por seu tamanho. E se você está obtendo o iterador para uma classe criada pelo usuário, pode fornecer um método size () nessa classe.

Resumindo, na situação em que você tem apenas o iterador, não há maneira melhor, mas muito mais frequentemente do que não, você tem acesso à coleção ou objeto subjacente a partir do qual pode ser capaz de obter o tamanho diretamente.

Michael Berry
fonte
Cuidado com o efeito colateral de Iterators.size(...)(mencionado em outros comentários abaixo e em java-doc): "Retorna o número de elementos restantes no iterador. O iterador ficará esgotado: seu método hasNext () retornará falso." Isso significa que você não pode mais usar o Iterator depois disso. Lists.newArrayList(some_iterator);pode ajudar.
MichaelCkr
91

Usando a biblioteca Guava :

int size = Iterators.size(iterator);

Internamente, ele itera todos os elementos, então é apenas por conveniência.

Andrejs
fonte
8
Isso é muito elegante. Lembre-se de que você está consumindo seu iterador (ou seja, o iterador ficará vazio depois)
lolski
1
Isso não é "computacionalmente rápido", é um método de conveniência que tem o efeito colateral indesejado de consumir o iterador.
Zak de
Você pode explicar como isso funciona? @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

Seu código apresentará uma exceção quando você chegar ao final do iterador. Você poderia fazer:

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

Se você tivesse acesso à coleção subjacente, seria capaz de chamar coll.size()...

EDITAR OK, você alterou ...

assilias
fonte
quão eficiente é isso? e se o iterador for como um milhão de valores?
Micro
4
@Micro tecnicamente, um iterador pode ser infinito - nesse caso, o loop continuará para sempre.
assylias
11

Você sempre terá que iterar. No entanto, você pode usar Java 8, 9 para fazer a contagem sem repetir explicitamente:

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

Aqui está um teste:

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

Isso imprime:

5

Interessante, você pode paralelizar a operação de contagem aqui, alterando o parallelsinalizador nesta chamada:

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

Usando a biblioteca Guava , outra opção é converter o Iterablepara um List.

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

Use-o se também precisar acessar os elementos do iterador após obter seu tamanho. Ao usar, Iterators.size()você não pode mais acessar os elementos iterados.

tashuhka
fonte
2
@LoveToCode Menos eficiente do que o exemplo da pergunta original
Inverno
2
Claro, criar um novo objeto com todos os elementos é mais lento do que apenas iterar e descartar. IMHO, esta solução é uma linha que melhora a legibilidade do código. Eu o uso muito para coleções com poucos elementos (até 1000) ou quando a velocidade não é um problema.
tashuhka
7

Se tudo o que você tem é o iterador, então não, não há maneira "melhor". Se o iterador vier de uma coleção, você poderá definir o tamanho.

Lembre-se de que o Iterator é apenas uma interface para percorrer valores distintos, você muito bem teria um 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");
        }
    };

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

Não há maneira mais eficiente se tudo o que você tem é o iterador. E se o iterador só puder ser usado uma vez, obter a contagem antes de obter o conteúdo do iterador é ... problemático.

A solução é alterar seu aplicativo para que não precise da contagem ou obter a contagem por algum outro meio. (Por exemplo, passe um em Collectionvez de Iterator...)

Stephen C
fonte
0

para Java 8 você pode usar,

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

O objeto iterator contém o mesmo número de elementos que sua coleção continha.

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

Mas em vez de obter o tamanho do iterador e iterar pelo índice 0 até esse tamanho, é melhor iterar por meio do método next () do iterador.

Chandra Sekhar
fonte
E se não tivermos a, mas apenas i?
Tvde1,