Jaki jest najlepszy sposób uzyskania liczby / długości / rozmiaru iteratora?


96

Czy istnieje „obliczeniowy” szybki sposób uzyskania liczby iteratora?

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

... wygląda na stratę cykli procesora.


2
Iterator niekoniecznie musi odpowiadać „zliczeniu” ...
Oliver Charlesworth

Iteratory są tym, czym są; aby iterować do następnego obiektu kolekcji (może to być coś takiego jak zestaw, tablica itp.) Dlaczego muszą podawać rozmiar, kiedy nie obchodzi ich, po co próbują iterować? 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

Odpowiedzi:


67

Jeśli masz właśnie iterator, musisz to zrobić - nie wie, ile elementów zostało do iteracji, więc nie możesz zapytać go o ten wynik. Istnieją metody narzędziowe, które wydają się to robić (na przykład Iterators.size()w guawie), ale pod spodem wykonują w przybliżeniu tę samą operację.

Jednak wiele iteratorów pochodzi z kolekcji, w przypadku których często można zapytać o ich rozmiar. A jeśli jest to klasa utworzona przez użytkownika, dla której otrzymujesz iterator, możesz poszukać metody size () dla tej klasy.

Krótko mówiąc, w sytuacji, gdy masz tylko iterator, nie ma lepszego sposobu, ale znacznie częściej niż nie masz dostępu do bazowej kolekcji lub obiektu, z którego możesz bezpośrednio uzyskać rozmiar.


Uważaj na efekt uboczny Iterators.size(...)(wspomniany w innych komentarzach poniżej oraz w java-doc): "Zwraca liczbę elementów pozostałych w iteratorze. Iterator zostanie wyczerpany: jego metoda hasNext () zwróci wartość false." Oznacza to, że nie możesz już używać Iteratora. Lists.newArrayList(some_iterator);może pomóc.
MichaelCkr

91

Korzystanie z biblioteki Guava :

int size = Iterators.size(iterator);

Wewnętrznie po prostu iteruje po wszystkich elementach, więc jest to tylko dla wygody.


8
To jest bardzo eleganckie. Pamiętaj tylko, że używasz swojego iteratora (tzn. Iterator będzie potem pusty)
lolski

1
Nie jest to „szybkie obliczeniowo”, jest to wygodna metoda, która ma niepożądany efekt uboczny używania iteratora.
Zak,

Czy możesz wyjaśnić, jak to działa? @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

Twój kod da ci wyjątek, gdy dojdziesz do końca iteratora. Mógłbyś:

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

Gdybyś miał dostęp do podstawowej kolekcji, mógłbyś zadzwonić coll.size()...

EDYTUJ OK, poprawiłeś ...


jak wydajne to jest? co, jeśli iterator jest jak milion wartości?
Micro

4
@Micro technicznie rzecz biorąc, iterator może być nieskończony - w takim przypadku pętla będzie trwać wiecznie.
assylias

12

Zawsze będziesz musiał iterować. Jednak możesz użyć Java 8, 9 do liczenia bez jawnego zapętlania:

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

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

To drukuje:

5

Co ciekawe, możesz zrównoleglać tutaj operację zliczania, zmieniając parallelflagę w tym wywołaniu:

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

8

Korzystając z biblioteki Guava , inną opcją jest przekonwertowanie Iterablepliku na plik List.

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

Użyj tego, jeśli potrzebujesz również uzyskać dostęp do elementów iteratora po uzyskaniu jego rozmiaru. Używając Iterators.size()nie masz już dostępu do iterowanych elementów.


2
@LoveToCode Mniej wydajne niż przykład w pierwotnym pytaniu
zima

2
Oczywiście, tworzenie nowego obiektu ze wszystkimi elementami jest wolniejsze niż zwykłe iterowanie i odrzucanie. IMHO, to rozwiązanie jest jednowierszowe, które poprawia czytelność kodu. Używam go często do kolekcji z kilkoma elementami (do 1000) lub gdy szybkość nie jest problemem.
tashuhka

7

Jeśli wszystko, co masz, to iterator, to nie, nie ma „lepszego” sposobu. Jeśli iterator pochodzi z kolekcji, możesz wybrać rozmiar.

Pamiętaj, że Iterator to tylko interfejs do przechodzenia przez różne wartości, bardzo dobrze byłoby mieć taki kod

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

lub

    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

Nie ma bardziej wydajnego sposobu, jeśli wszystko, co masz, to iterator. A jeśli iterator może być użyty tylko raz, to uzyskanie licznika przed uzyskaniem zawartości iteratora jest ... problematyczne.

Rozwiązaniem jest albo zmiana aplikacji, aby nie potrzebowała liczenia, albo uzyskanie liczby w inny sposób. (Na przykład podaj Collectionraczej niż Iterator...)


0

dla Java 8 możesz użyć,

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

-5

obiekt iterator zawiera taką samą liczbę elementów, jaką zawierała Twoja kolekcja.

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

Ale zamiast pobierać rozmiar iteratora i iterować przez indeks 0 do tego rozmiaru, lepiej jest wykonać iterację metodą next () iteratora.


A jeśli nie mamy a, ale tylko i?
Tvde1
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.