Uzyskaj rozmiar iterowalnego w Javie

88

Muszę obliczyć liczbę elementów Iterablew języku Java. Wiem, że potrafię to zrobić:

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

Mógłbym też zrobić coś takiego, ponieważ nie potrzebuję już obiektów w Iterable:

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

Benchmark na małą skalę nie wykazał dużej różnicy w wydajności, żadnych komentarzy ani innych pomysłów na ten problem?

js84
źródło
Czemu? Albo jest to naprawdę zły pomysł, ponieważ oba są O (N). Powinieneś unikać konieczności dwukrotnego iterowania kolekcji.
Markiz Lorne
3
Twój drugi nie powinien nawet działać. Nie możesz zadzwonić remove()bez next()wcześniejszego wezwania .
Louis Wasserman,

Odpowiedzi:

123

TL; DR: Użyj metody narzędziowej Iterables.size(Iterable)wielkiej biblioteki Guava .

Z dwóch fragmentów kodu powinieneś użyć pierwszego, ponieważ drugi usunie wszystkie elementy z values, więc później jest pusty. Zmiana struktury danych dla prostego zapytania, takiego jak jego rozmiar, jest bardzo nieoczekiwana.

Wydajność zależy od struktury danych. Jeśli jest to na przykład w rzeczywistości a ArrayList, usuwanie elementów od początku (co robi twoja druga metoda) jest bardzo powolne (obliczenie rozmiaru staje się O (n * n) zamiast O (n) tak, jak powinno).

Ogólnie rzecz biorąc, jeśli istnieje szansa, że valuesfaktycznie jest to a, Collectiona nie tylko Iterable, sprawdź to i zadzwoń size()w przypadku:

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

Wezwanie do size()woli zwykle znacznie szybciej niż zliczanie liczby elementów, a ten trik jest dokładnie to, co Iterables.size(Iterable)z Guava robi dla Ciebie.

Philipp Wendler
źródło
Mówi, że nie interesują go elementy i nie dba o to, czy zostaną usunięte.
aioobe
Małe wyjaśnienie: usunie elementy zvalues
Miquel
1
Ale inne części kodu mogą nadal używać tej samej iterowalnej. A jeśli nie teraz, to może w przyszłości.
Philipp Wendler,
Proponuję, aby odpowiedź Google Guava była bardziej widoczna. Nie ma powodu, aby ludzie ponownie pisali ten kod, nawet jeśli jest to trywialne.
Stephen Harrison
8
Jeśli używasz Java 8, utwórz Stream i policz w nim element jako: Stream.of (myIterable) .count ()
FrankBr
41

Jeśli pracujesz z Javą 8, możesz użyć:

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

zadziała tylko wtedy, gdy iterowalne źródło ma określony rozmiar. Większość Spliterators for Collections to zrobić, ale możesz mieć problemy, jeśli pochodzą one z HashSetlub ResultSetna przykład.

Możesz sprawdzić javadoc tutaj.

Jeśli Java 8 nie wchodzi w grę lub nie wiesz, skąd pochodzi iterowalność, możesz zastosować to samo podejście co 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;
    }
ArnaudR
źródło
1
Niech ktoś da temu facetowi medal.
Pramesh Bajracharya
U mnie to nie działa w przypadku Sort . Odpowiedź New Bee działa dla mnie.
Sabir Khan
18

To może trochę za późno, ale może komuś pomóc. Natknąłem się na podobny problem Iterablew mojej bazie kodu i rozwiązaniem było użycie for eachbez jawnego wywołania values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}
pilot
źródło
2
To dla mnie intuicyjne podejście, które potrafię docenić. Użyłem go tylko kilka minut temu. Dzięki.
Matt Cremeens,
2
Tak, to intuicyjne, ale niestety musisz ukryć nieużywane ostrzeżenie dla wartości ...
Nils-o-mat
Dzięki za to rozwiązanie rzeczywiście zwraca rozmiar obiektu. Jedyną wadą jest to, że jeśli chcesz później powtórzyć ten sam obiekt, najpierw powinieneś jakoś go zresetować.
Zsolti
7

Możesz przesłać swoją iterowalną listę na listę, a następnie użyć na niej .size ().

Lists.newArrayList(iterable).size();

Dla jasności powyższa metoda będzie wymagać następującego importu:

import com.google.common.collect.Lists;
przekąski
źródło
2
Listy to zajęcia z guawy
Paul Jackson
6

Ściśle mówiąc, Iterable nie ma rozmiaru. Pomyśl o strukturze danych jak o cyklu.

I pomyśl o następującej iterowalnej instancji, bez rozmiaru:

    new Iterable(){

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

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

                @Override
                public Object next() {
                    return fetchDataFromExternalSystem();
                }};
        }};
卢 声 远 Shengyuan Lu
źródło
2

Wybrałbym it.next()z prostego powodu, który next()jest gwarantowany do zaimplementowania, podczas gdy remove()jest to operacja opcjonalna.

E next()

Zwraca następny element w iteracji.

void remove()

Usuwa z podstawowej kolekcji ostatni element zwrócony przez iterator (operacja opcjonalna) .

aioobe
źródło
Chociaż ta odpowiedź jest technicznie poprawna, jest dość myląca. Wywołanie removezostało już zauważone jako niewłaściwy sposób liczenia elementów an Iterator, więc tak naprawdę nie ma znaczenia, czy rzecz, której nie zamierzamy zrobić, jest zaimplementowana, czy nie.
Stephen Harrison
1
Która dokładnie część odpowiedzi jest myląca? A w okolicznościach, w których remove jest wdrożony, dlaczego byłoby źle z niego korzystać? Przy okazji, głosy przeciwne są zwykle używane do złych odpowiedzi lub odpowiedzi, które dają złą radę. Nie rozumiem, jak ta odpowiedź kwalifikuje się do czegoś takiego.
aioobe
2

java 8 i nowsze

StreamSupport.stream(data.spliterator(), false).count();
New Bee
źródło
0

Jak dla mnie to tylko różne metody. Pierwsza z nich pozostawia obiekt, na którym iterujesz, bez zmian, podczas gdy sekundy pozostawiają go pustym. Pytanie brzmi, co chcesz robić. Złożoność usuwania opiera się na implementacji iterowalnego obiektu. Jeśli używasz Kolekcji - po prostu uzyskaj rozmiar taki, jaki zaproponował Kazekage Gaara - zazwyczaj jest to najlepsze podejście pod względem wydajności.

Mark Bramnik
źródło
-2

Dlaczego po prostu nie użyjesz size()metody na swoim, Collectionaby uzyskać liczbę elementów?

Iterator służy tylko do iteracji, nic więcej.

Kazekage Gaara
źródło
4
Kto powiedział, że używa kolekcji? :-)
aioobe
Używasz iteratora, który obsługuje usuwanie elementów. Jeśli wybierzesz rozmiar przed wejściem w pętlę iteratora, musisz uważać przy usuwaniu i odpowiednio zaktualizować wartość.
Miquel
1
Przykład tego, dlaczego może nie mieć kolekcji do sprawdzenia pod kątem rozmiaru: może wywoływać metodę API innej firmy, która zwraca tylko Iterowalną.
SteveT
2
Ta odpowiedź myli Iteratori Iterable. Zobacz głosowaną odpowiedź, aby znaleźć właściwy sposób.
Stephen Harrison
-4

Zamiast używać pętli i zliczać każdy element lub korzystać z biblioteki innej firmy, możemy po prostu wpisać iterowalną iterację w ArrayList i uzyskać jej rozmiar.

((ArrayList) iterable).size();
fatimasajjad
źródło
3
Jednak nie wszystkie elementy iteracyjne można rzutować na ArrayList
Alexander Mills,