Qual è il modo migliore per ottenere il conteggio / lunghezza / dimensione di un iteratore?


96

Esiste un modo rapido "computazionalmente" per ottenere il conteggio di un iteratore?

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

... sembra uno spreco di cicli della CPU.


2
Un iteratore non corrisponde necessariamente a qualcosa con un "conteggio" ...
Oliver Charlesworth

Gli iteratori sono ciò che sono; per iterare al prossimo oggetto di una collezione (può essere qualsiasi cosa come set, array, ecc.) Perché hanno bisogno di dire la dimensione quando non si preoccupano per cosa stanno cercando di iterare? 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

Risposte:


67

Se hai appena l'iteratore, allora è quello che dovrai fare: non sa quanti elementi sono rimasti su cui iterare, quindi non puoi interrogarlo per quel risultato. Ci sono metodi di utilità che sembrano fare questo (come Iterators.size()in Guava), ma sotto stanno solo eseguendo approssimativamente la stessa operazione.

Tuttavia, molti iteratori provengono da raccolte, che è spesso possibile interrogare per la loro dimensione. E se si tratta di una classe creata dall'utente per la quale stai ottenendo l'iteratore, potresti cercare di fornire un metodo size () su quella classe.

In breve, nella situazione in cui hai solo l'iteratore, non c'è modo migliore, ma molto più spesso di quanto non hai accesso alla raccolta o all'oggetto sottostante da cui potresti essere in grado di ottenere direttamente la dimensione.


Attenzione all'effetto collaterale di Iterators.size(...)(menzionato in altri commenti qui sotto e in java-doc): "Restituisce il numero di elementi rimanenti nell'iteratore. L'iteratore verrà lasciato esaurito: il suo metodo hasNext () restituirà false." Ciò significa che in seguito non potrai più utilizzare l'iteratore. Lists.newArrayList(some_iterator);posso aiutare.
MichaelCkr

91

Utilizzando la libreria Guava :

int size = Iterators.size(iterator);

Internamente itera solo su tutti gli elementi, quindi è solo per comodità.


8
Questo è molto elegante. Ricorda solo che stai consumando il tuo iteratore (cioè, l'iteratore sarà vuoto in seguito)
lolski

1
Questo non è "veloce dal punto di vista computazionale", questo è un metodo conveniente che ha l'effetto collaterale indesiderato di consumare l'iteratore.
Zak

Puoi spiegare come funziona? @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

Il tuo codice ti darà un'eccezione quando raggiungi la fine dell'iteratore. Potresti fare:

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

Se avessi accesso alla raccolta sottostante, potresti chiamare coll.size()...

EDIT OK hai modificato ...


quanto è efficiente questo? e se l'iteratore fosse come un milione di valori?
Micro

4
@Micro tecnicamente un iteratore potrebbe essere infinito, nel qual caso il ciclo continuerà per sempre.
assylias

11

Dovrai sempre iterare. Eppure puoi usare Java 8, 9 per fare il conteggio senza loop esplicitamente:

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

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

Questo stampa:

5

Abbastanza interessante puoi parallelizzare l'operazione di conteggio qui cambiando il parallelflag su questa chiamata:

long count = StreamSupport.stream(newIterable.spliterator(), *true*).count();

8

Usando la libreria Guava , un'altra opzione è convertire il file Iterablein un file List.

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

Usalo se hai bisogno anche di accedere agli elementi dell'iteratore dopo aver ottenuto la sua dimensione. Usando Iterators.size()non puoi più accedere agli elementi iterati.


2
@LoveToCode Meno efficiente dell'esempio sulla domanda originale
Inverno

2
Certo, creare un nuovo oggetto con tutti gli elementi è più lento del semplice iterare e scartare. IMHO, questa soluzione è una riga che migliora la leggibilità del codice. Lo uso molto per raccolte con pochi elementi (fino a 1000) o quando la velocità non è un problema.
tashuhka

7

Se tutto ciò che hai è l'iteratore, allora no, non esiste un modo "migliore". Se l'iteratore proviene da una raccolta, potresti utilizzarlo per dimensioni.

Tieni presente che Iterator è solo un'interfaccia per attraversare valori distinti, dovresti benissimo avere un codice come questo

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

4

Non esiste un modo più efficiente, se tutto ciò che hai è l'iteratore. E se l'iteratore può essere utilizzato solo una volta, ottenere il conteggio prima di ottenere i contenuti dell'iteratore è ... problematico.

La soluzione è modificare l'applicazione in modo che non abbia bisogno del conteggio o ottenere il conteggio con altri mezzi. (Ad esempio, passa a Collectionanziché Iterator...)


0

per Java 8 potresti usare,

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

-5

L'oggetto iteratore contiene lo stesso numero di elementi contenuti nella tua raccolta.

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

Ma invece di ottenere la dimensione dell'iteratore e iterare attraverso l'indice 0 a quella dimensione, è meglio iterare attraverso il metodo next () dell'iteratore.


E se non lo avessimo a, ma solo i?
Tvde1
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.