Ottieni le dimensioni di un Iterable in Java


88

Ho bisogno di capire il numero di elementi in un Iterablein Java. So che posso fare questo:

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

Potrei anche fare qualcosa di simile, perché non ho più bisogno degli oggetti in Iterable:

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

Un benchmark su piccola scala non ha mostrato molta differenza di prestazioni, commenti o altre idee per questo problema?


Perché? O è una pessima idea, poiché entrambi sono O (N). Dovresti cercare di evitare di dover iterare due volte la raccolta.
Marchese di Lorne

3
Il tuo secondo non dovrebbe nemmeno funzionare. Non puoi chiamare remove()senza chiamare in next()anticipo.
Louis Wasserman

Risposte:


123

TL; DR: utilizza il metodo Iterables.size(Iterable)di utilità della grande libreria Guava .

Dei tuoi due frammenti di codice, dovresti usare il primo, perché il secondo rimuoverà tutti gli elementi da values, quindi sarà vuoto in seguito. La modifica di una struttura dati per una query semplice come la sua dimensione è molto inaspettata.

Per le prestazioni, questo dipende dalla struttura dei dati. Se ad esempio è an ArrayList, rimuovere gli elementi dall'inizio (quello che sta facendo il tuo secondo metodo) è molto lento (il calcolo della dimensione diventa O (n * n) invece di O (n) come dovrebbe essere).

In generale, se esiste la possibilità che valuessia effettivamente un Collectione non solo un Iterable, controllare questo e chiamare size()nel caso:

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

La chiamata a size()di solito è molto più veloce di contare il numero di elementi, e questo trucco è esattamente quello Iterables.size(Iterable)di Guava fa per voi.


Dice che non è interessato agli elementi e non gli importa se vengono rimossi.
aioobe

Piccola precisazione: rimuoverà gli elementi davalues
Miquel

1
Ma altre parti del codice potrebbero ancora utilizzare lo stesso Iterable. E se non ora, forse in futuro.
Philipp Wendler

Suggerisco di rendere la risposta di Google Guava più prominente. Non c'è motivo per costringere le persone a scrivere di nuovo questo codice, anche se è banale.
Stephen Harrison

8
Se usi Java 8, crea uno Stream e conta l'elemento come: Stream.of (myIterable) .count ()
FrankBr

41

Se stai lavorando con java 8 puoi usare:

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

funzionerà solo se la sorgente iterabile ha una dimensione determinata. La maggior parte degli spliterator per le raccolte lo farà, ma potresti avere problemi se proviene da HashSeto, ResultSetad esempio.

Puoi controllare il javadoc qui.

Se Java 8 non è un'opzione o se non sai da dove proviene l'iterabile, puoi utilizzare lo stesso approccio di 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;
    }

1
Qualcuno dia a questo ragazzo una medaglia.
Pramesh Bajracharya

Non funziona per me per Sort . La risposta di New Bee funziona per me.
Sabir Khan il

18

Forse è un po 'tardi, ma potrebbe aiutare qualcuno. Mi sono imbattuto in un problema simile Iterablenella mia base di codice e la soluzione era quella di utilizzare for eachsenza chiamare esplicitamente values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}

2
Questo è, per me, un approccio intuitivo, che posso apprezzare. L'ho usato solo pochi minuti fa. Grazie.
Matt Cremeens

2
Sì, è intuitivo, ma purtroppo devi sopprimere un avvertimento inutilizzato per il valore ...
Nils-o-mat,

Grazie per questa soluzione, restituisce effettivamente la dimensione dell'oggetto. L'unico aspetto negativo è che se vuoi ripetere più tardi lo stesso oggetto, prima dovresti ripristinarlo in qualche modo.
Zsolti

7

Puoi trasmettere il tuo iterabile a un elenco quindi usare .size () su di esso.

Lists.newArrayList(iterable).size();

Per motivi di chiarezza, il metodo sopra richiederà la seguente importazione:

import com.google.common.collect.Lists;

2
Lists è una lezione di Guava
Paul Jackson

6

A rigor di termini, Iterable non ha dimensioni. Pensa alla struttura dei dati come un ciclo.

E pensa alla seguente istanza Iterable, Nessuna dimensione:

    new Iterable(){

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

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

                @Override
                public Object next() {
                    return fetchDataFromExternalSystem();
                }};
        }};

2

Vorrei it.next()per il semplice motivo che next()è garantito per essere implementato, mentre remove()è un'operazione opzionale.

E next()

Restituisce l'elemento successivo nell'iterazione.

void remove()

Rimuove dalla raccolta sottostante l'ultimo elemento restituito dall'iteratore (operazione facoltativa) .


Sebbene questa risposta sia tecnicamente corretta, è piuttosto fuorviante. La chiamata removeè già stata notata come il modo sbagliato per contare gli elementi di un Iterator, quindi non importa se la cosa che non faremo è implementata o meno.
Stephen Harrison

1
Esattamente quale parte della risposta è fuorviante? E nella circostanza in cui remove è implementato, perché sarebbe sbagliato usarlo? A proposito, i voti negativi sono tipicamente usati per risposte sbagliate o risposte che danno cattivi consigli. Non riesco a vedere come questa risposta si qualifichi per tutto ciò.
aioobe

2

java 8 e versioni successive

StreamSupport.stream(data.spliterator(), false).count();

0

Per quanto mi riguarda, questi sono solo metodi diversi. Il primo lascia l'oggetto su cui stai iterando invariato, mentre i secondi lo lascia vuoto. La domanda è cosa vuoi fare. La complessità della rimozione si basa sull'implementazione del tuo oggetto iterabile. Se stai usando le collezioni, ottieni semplicemente la dimensione come è stata proposta da Kazekage Gaara - di solito è il miglior approccio in termini di prestazioni.


-2

Perché non usi semplicemente il size()metodo sul tuo Collectionper ottenere il numero di elementi?

Iterator è solo pensato per iterare, nient'altro.


4
Chi dice che sta usando una collezione? :-)
aioobe

Stai usando un iteratore, che supporta la rimozione di elementi. Se prendi la dimensione prima di entrare nel ciclo dell'iteratore, devi fare attenzione con le tue rimozioni e aggiornare il valore di conseguenza.
Miquel

1
Un esempio del motivo per cui potrebbe non avere una raccolta da esaminare per la dimensione: potrebbe chiamare un metodo API di terze parti che restituisce solo un Iterable.
SteveT

2
Questa risposta confonde Iteratore Iterable. Vedi la risposta votata per il modo corretto.
Stephen Harrison

-4

Invece di usare i loop e contare ogni elemento o usare una libreria di terze parti, possiamo semplicemente digitare l'iterabile in ArrayList e ottenere la sua dimensione.

((ArrayList) iterable).size();

3
Non tutti gli iterabili possono essere trasmessi a ArrayList tho
Alexander Mills
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.