Holen Sie sich die Größe eines Iterable in Java

86

Ich muss die Anzahl der Elemente in einem Iterablein Java herausfinden . Ich weiß, ich kann dies tun:

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

Ich könnte auch so etwas machen, weil ich die Objekte im Iterable nicht mehr brauche:

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

Ein kleiner Benchmark zeigte keinen großen Leistungsunterschied, keine Kommentare oder andere Ideen für dieses Problem?

js84
quelle
Warum? Beides ist eine wirklich schlechte Idee, da beide O (N) sind. Sie sollten versuchen, zu vermeiden, dass die Sammlung zweimal wiederholt werden muss.
Marquis von Lorne
3
Dein zweiter sollte nicht einmal funktionieren. Sie können nicht anrufen, remove()ohne next()vorher anzurufen .
Louis Wasserman

Antworten:

122

TL; DR: Verwenden Sie die Utility-Methode Iterables.size(Iterable)der großen Guava- Bibliothek.

Von Ihren beiden Codefragmenten sollten Sie das erste verwenden, da das zweite alle Elemente entfernt values, sodass es danach leer ist. Das Ändern einer Datenstruktur für eine einfache Abfrage wie ihre Größe ist sehr unerwartet.

Die Leistung hängt von Ihrer Datenstruktur ab. Wenn es sich zum Beispiel tatsächlich um ein ArrayListElement handelt, ist das Entfernen von Elementen von Anfang an (was Ihre zweite Methode tut) sehr langsam (die Berechnung der Größe wird zu O (n * n) anstelle von O (n), wie es sein sollte).

Wenn die Möglichkeit besteht, dass valueses sich tatsächlich um eine Collectionund nicht nur um eine handelt Iterable, überprüfen Sie dies im Allgemeinen und rufen Sie size()an, falls:

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

Der Aufruf size()wird in der Regel viel schneller sein als die Anzahl der Elemente zu zählen, und dieser Trick ist genau das, was Iterables.size(Iterable)von Guava für Sie tut.

Philipp Wendler
quelle
Er sagt, dass er sich nicht für die Elemente interessiert und es ihm egal ist, ob sie entfernt werden.
Aioobe
Kleine Klarstellung: Es werden die Elemente ausvalues
Miquel
1
Andere Teile des Codes verwenden jedoch möglicherweise immer noch dieselbe Iterable. Und wenn nicht jetzt, vielleicht in der Zukunft.
Philipp Wendler
Ich schlage vor, die Antwort von Google Guava bekannter zu machen. Es gibt keinen Grund, die Leute dazu zu bringen, diesen Code erneut zu schreiben, obwohl er trivial ist.
Stephen Harrison
8
Wenn Sie Java 8 verwenden, erstellen Sie ein Stream- und Count-Element darin als: Stream.of (myIterable) .count ()
FrankBr
40

Wenn Sie mit Java 8 arbeiten, können Sie Folgendes verwenden:

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

Dies funktioniert nur, wenn die iterierbare Quelle eine bestimmte Größe hat. Die meisten Spliteratoren für Sammlungen werden, aber Sie können Probleme haben, wenn es von einem HashSetoder ResultSetzum Beispiel kommt.

Sie können den Javadoc hier überprüfen .

Wenn Java 8 keine Option ist oder Sie nicht wissen, woher das iterable stammt, können Sie den gleichen Ansatz wie Guave verwenden:

  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
quelle
1
Jemand, gib diesem Kerl eine Medaille.
Pramesh Bajracharya
Bei Sort funktioniert das bei mir nicht . Antwort von New Bee funktioniert für mich.
Sabir Khan
17

Dies ist vielleicht etwas spät, kann aber jemandem helfen. Ich stoße auf ein ähnliches Problem Iterablein meiner Codebasis und die Lösung bestand darin, for eachohne expliziten Aufruf zu verwenden values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}
Pilot
quelle
2
Dies ist für mich ein intuitiver Ansatz, den ich schätzen kann. Ich habe es gerade vor ein paar Minuten benutzt. Vielen Dank.
Matt Cremeens
2
Ja, es ist intuitiv, aber leider müssen Sie eine unbenutzte Warnung für Wert unterdrücken ...
Nils-o-mat
Vielen Dank für diese Lösung, die tatsächlich die Größe des Objekts zurückgibt. Die einzige Kehrseite ist, dass Sie, wenn Sie später noch einmal durch dasselbe Objekt iterieren möchten, es zuerst irgendwie zurücksetzen sollten.
Zsolti
7

Sie können Ihre Iterable in eine Liste umwandeln und dann .size () darauf verwenden.

Lists.newArrayList(iterable).size();

Aus Gründen der Klarheit erfordert die obige Methode den folgenden Import:

import com.google.common.collect.Lists;
Snacks
quelle
2
Listen ist eine Guave-Klasse
Paul Jackson
6

Genau genommen hat Iterable keine Größe. Stellen Sie sich die Datenstruktur wie einen Zyklus vor.

Und denken Sie an die folgende Iterable-Instanz: Keine Größe:

    new Iterable(){

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

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

                @Override
                public Object next() {
                    return fetchDataFromExternalSystem();
                }};
        }};
Gy 声 远 Shengyuan Lu
quelle
2

Ich würde mich für it.next()den einfachen Grund entscheiden, der next()garantiert implementiert wird, während remove()es sich um eine optionale Operation handelt.

E next()

Gibt das nächste Element in der Iteration zurück.

void remove()

Entfernt aus der zugrunde liegenden Sammlung das letzte vom Iterator zurückgegebene Element (optionale Operation) .

aioobe
quelle
Obwohl diese Antwort technisch korrekt ist, ist sie ziemlich irreführend. Das Aufrufen removewurde bereits als die falsche Methode zum Zählen von Elementen von a bezeichnet. IteratorEs spielt also keine Rolle, ob das, was wir nicht tun werden, implementiert ist oder nicht.
Stephen Harrison
1
Welcher Teil der Antwort ist irreführend? Und unter dem Umstand, remove wird implementiert, warum wäre es falsch, es zu benutzen? Übrigens werden Abstimmungen normalerweise für falsche Antworten oder Antworten verwendet, die schlechte Ratschläge geben. Ich kann nicht sehen, wie sich diese Antwort für irgendetwas davon qualifiziert.
Aioobe
2

Java 8 und höher

StreamSupport.stream(data.spliterator(), false).count();
Neue Biene
quelle
0

Für mich sind dies nur verschiedene Methoden. Der erste lässt das Objekt, auf dem Sie iterieren, unverändert, während der zweite es leer lässt. Die Frage ist, was Sie tun möchten. Die Komplexität des Entfernens basiert auf der Implementierung Ihres iterierbaren Objekts. Wenn Sie Sammlungen verwenden - erhalten Sie einfach die Größe, wie sie von Kazekage Gaara vorgeschlagen wurde -, ist dies normalerweise der beste Ansatz in Bezug auf die Leistung.

Mark Bramnik
quelle
-2

Warum verwenden Sie nicht einfach die size()Methode auf Ihrem Collection, um die Anzahl der Elemente zu ermitteln?

Iterator soll nur iterieren, sonst nichts.

Kazekage Gaara
quelle
4
Wer sagt, dass er eine Sammlung benutzt? :-)
aioobe
Sie verwenden einen Iterator, der das Entfernen von Elementen unterstützt. Wenn Sie die Größe vor dem Betreten der Iteratorschleife ermitteln, müssen Sie beim Entfernen vorsichtig sein und den Wert entsprechend aktualisieren.
Miquel
1
Ein Beispiel dafür, warum er möglicherweise keine Sammlung hat, um nach Größe zu suchen: Möglicherweise ruft er eine API-Methode eines Drittanbieters auf, die nur eine Iterable zurückgibt.
SteveT
2
Diese Antwort verwirrt Iteratorund Iterable. Siehe die abgestimmte Antwort für den richtigen Weg.
Stephen Harrison
-4

Anstatt Schleifen zu verwenden und jedes Element zu zählen oder eine Bibliothek eines Drittanbieters zu verwenden, können wir die Iterable einfach in ArrayList typisieren und ihre Größe ermitteln.

((ArrayList) iterable).size();
fatimasajjad
quelle
3
Nicht alle iterables können in ArrayList tho
Alexander Mills