Wie iteriere ich durch SparseArray?

311

Gibt es eine Möglichkeit, über Java SparseArray (für Android) zu iterieren? Früher habe ich sparsearrayleicht Werte nach Index erhalten. Ich konnte keinen finden.

Ruzanna
quelle
30
Wow, sprechen Sie über eine völlig ungeliebte Klasse , entspricht ZERO-Sammlungsschnittstellen ...
1
Sie können ein verwenden, mit TreeMap<Integer, MyType>dem Sie nach Schlüssel sortieren können. Wie bereits erwähnt, ist SparseArray effizienter als eine HashMap, erlaubt jedoch keine Iteration.
John B
2
Es ist sehr, sehr unwahrscheinlich, dass die Leistung des von Ihnen ausgewählten Kartenimplikats der Engpass in Ihrer App ist.
Jeffrey Blattman
3
@ JeffreyBlattman bedeutet nicht, dass wir die Verwendung der richtigen Struktur vermeiden sollten, wenn dies eindeutig angemessen ist.
frostymarvelous
1
@frostymarvelous sagen, es ist ZWEIMAL so schnell, das bedeutet wahrscheinlich eine Einsparung von weniger als 10 ms. Sind 10 ms im größeren Schema der App relevant? Lohnt es sich, eine suboptimale Schnittstelle zu verwenden, die schwerer zu verstehen und zu warten ist? Ich kenne die Antwort auf diese Dinge nicht, aber die Antwort lautet nicht "absolut spärliches Array verwenden, unabhängig davon".
Jeffrey Blattman

Antworten:

537

Scheint, als hätte ich die Lösung gefunden. Ich hatte die keyAt(index)Funktion nicht richtig bemerkt .

Also gehe ich mit so etwas:

for(int i = 0; i < sparseArray.size(); i++) {
   int key = sparseArray.keyAt(i);
   // get the object by the key.
   Object obj = sparseArray.get(key);
}
Ruzanna
quelle
25
In der Dokumentation heißt es: "keyAt (int index) Gibt bei einem Index im Bereich von 0 ... size () - 1 den Schlüssel aus der indexierten Schlüsselwertzuordnung zurück, die dieses SparseArray speichert." So funktioniert es auch für den von Ihnen beschriebenen Fall.
Ruzanna
12
Es ist besser, die Größe des Arrays vorab zu berechnen und einen konstanten Wert in der Schleife zu verwenden.
Dmitry Zaytsev
25
Wäre es nicht einfacher, hier direkt die Funktion valueAt zu verwenden?
Milan Krstic
34
Dies würde auch innerhalb der Schleife funktionieren:Object obj = sparseArray.valueAt(i);
Florian
27
valueAt(i)ist schneller als get(key), weil valueAt(i)und keyAt(i)beide O (1) sind , aber get(key)ist O (log2 n) , also würde ich sicher immer verwenden valueAt.
Mecki
180

Wenn Sie sich nicht für die Schlüssel interessieren, valueAt(int)können Sie beim Durchlaufen des spärlichen Arrays direkt auf die Werte zugreifen.

for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
    Object obj = sparseArray.valueAt(i);
}
Pogo Lin
quelle
7
Die Verwendung von valueAt () ist nützlich (und schneller als die akzeptierte Lösung), wenn Ihre Iteration sich nicht um die Schlüssel kümmert, dh: eine Schleife, die Vorkommen eines bestimmten Werts zählt.
Sogger
2
Nehmen Sie sparseArray.size()eine Variable auf, damit sie nicht size()jedes Mal aufgerufen wird.
Pratik Butani
4
Es ist überflüssig, size () in eine Variable zu kopieren. Einfach zu überprüfen, ob Sie sich nur den Code der size () -Methode ansehen. Ich kann nicht verstehen, warum Sie es nicht getan haben, bevor Sie solche Dinge vorgeschlagen haben ... Ich erinnere mich an eine Zeit vor 20 Jahren, als wir einfache verknüpfte Listen hatten, die wirklich jedes Mal ihre Größe zählen mussten, wenn Sie sie danach fragten, aber ich glaube nicht dass solche Dinge noch existieren ...
Der unglaubliche
Ist dies garantiert in Schlüsselreihenfolge?
HughHughTeotl
18

Oder Sie erstellen einfach Ihren eigenen ListIterator:

public final class SparseArrayIterator<E> implements ListIterator<E> {

private final SparseArray<E> array;
private int cursor;
private boolean cursorNowhere;

/**
 * @param array
 *            to iterate over.
 * @return A ListIterator on the elements of the SparseArray. The elements
 *         are iterated in the same order as they occur in the SparseArray.
 *         {@link #nextIndex()} and {@link #previousIndex()} return a
 *         SparseArray key, not an index! To get the index, call
 *         {@link android.util.SparseArray#indexOfKey(int)}.
 */
public static <E> ListIterator<E> iterate(SparseArray<E> array) {
    return iterateAt(array, -1);
}

/**
 * @param array
 *            to iterate over.
 * @param key
 *            to start the iteration at. {@link android.util.SparseArray#indexOfKey(int)}
 *            < 0 results in the same call as {@link #iterate(android.util.SparseArray)}.
 * @return A ListIterator on the elements of the SparseArray. The elements
 *         are iterated in the same order as they occur in the SparseArray.
 *         {@link #nextIndex()} and {@link #previousIndex()} return a
 *         SparseArray key, not an index! To get the index, call
 *         {@link android.util.SparseArray#indexOfKey(int)}.
 */
public static <E> ListIterator<E> iterateAtKey(SparseArray<E> array, int key) {
    return iterateAt(array, array.indexOfKey(key));
}

/**
 * @param array
 *            to iterate over.
 * @param location
 *            to start the iteration at. Value < 0 results in the same call
 *            as {@link #iterate(android.util.SparseArray)}. Value >
 *            {@link android.util.SparseArray#size()} set to that size.
 * @return A ListIterator on the elements of the SparseArray. The elements
 *         are iterated in the same order as they occur in the SparseArray.
 *         {@link #nextIndex()} and {@link #previousIndex()} return a
 *         SparseArray key, not an index! To get the index, call
 *         {@link android.util.SparseArray#indexOfKey(int)}.
 */
public static <E> ListIterator<E> iterateAt(SparseArray<E> array, int location) {
    return new SparseArrayIterator<E>(array, location);
}

private SparseArrayIterator(SparseArray<E> array, int location) {
    this.array = array;
    if (location < 0) {
        cursor = -1;
        cursorNowhere = true;
    } else if (location < array.size()) {
        cursor = location;
        cursorNowhere = false;
    } else {
        cursor = array.size() - 1;
        cursorNowhere = true;
    }
}

@Override
public boolean hasNext() {
    return cursor < array.size() - 1;
}

@Override
public boolean hasPrevious() {
    return cursorNowhere && cursor >= 0 || cursor > 0;
}

@Override
public int nextIndex() {
    if (hasNext()) {
        return array.keyAt(cursor + 1);
    } else {
        throw new NoSuchElementException();
    }
}

@Override
public int previousIndex() {
    if (hasPrevious()) {
        if (cursorNowhere) {
            return array.keyAt(cursor);
        } else {
            return array.keyAt(cursor - 1);
        }
    } else {
        throw new NoSuchElementException();
    }
}

@Override
public E next() {
    if (hasNext()) {
        if (cursorNowhere) {
            cursorNowhere = false;
        }
        cursor++;
        return array.valueAt(cursor);
    } else {
        throw new NoSuchElementException();
    }
}

@Override
public E previous() {
    if (hasPrevious()) {
        if (cursorNowhere) {
            cursorNowhere = false;
        } else {
            cursor--;
        }
        return array.valueAt(cursor);
    } else {
        throw new NoSuchElementException();
    }
}

@Override
public void add(E object) {
    throw new UnsupportedOperationException();
}

@Override
public void remove() {
    if (!cursorNowhere) {
        array.remove(array.keyAt(cursor));
        cursorNowhere = true;
        cursor--;
    } else {
        throw new IllegalStateException();
    }
}

@Override
public void set(E object) {
    if (!cursorNowhere) {
        array.setValueAt(cursor, object);
    } else {
        throw new IllegalStateException();
    }
}
}
0101100101
quelle
9
IMHO scheint es ein bisschen übertrieben. Es ist fantastisch tho
hrules6872
12

Einfach wie Torte. Stellen Sie einfach sicher, dass Sie die Arraygröße abrufen, bevor Sie die Schleife tatsächlich ausführen.

for(int i = 0, arraySize= mySparseArray.size(); i < arraySize; i++) {
   Object obj = mySparseArray.get(/* int key = */ mySparseArray.keyAt(i));
}

Hoffe das hilft.

Pascal
quelle
11

Für jeden, der Kotlin verwendet, ist der mit Abstand einfachste Weg, über ein SparseArray zu iterieren, ehrlich: Verwenden Sie die Kotlin-Erweiterung von Anko oder Android KTX ! (Dank an Yazazzello für den Hinweis auf Android KTX)

Einfach anrufen forEach { i, item -> }

0101100101
quelle
Ja, du hast tatsächlich recht. Mein schlechtes, ich schaute auf die Tags und dachte, dass Kotlin nicht hier sein sollte. Aber jetzt ein zweiter Gedanke, dass diese Antwort ein guter Hinweis auf Kotlin selbst ist. Obwohl ich anstelle von Anko empfehlen würde, android.github.io/android-ktx/core-ktx zu verwenden (wenn Sie Ihre Antwort freundlicherweise bearbeiten und android-ktx hinzufügen könnten, werde ich sie verbessern)
Yazazzello
@ Yazazzello hey ich wusste nicht mal über Android KTX, guter Punkt!
0101100101
7

Zum Entfernen aller Elemente aus der SparseArrayVerwendung führt die obige Schleife zu Exception.

Um dies zu vermeiden, befolgen Sie den folgenden Code, um alle Elemente aus der SparseArrayVerwendung normaler Schleifen zu entfernen

private void getValues(){      
    for(int i=0; i<sparseArray.size(); i++){
          int key = sparseArray.keyAt(i);
          Log.d("Element at "+key, " is "+sparseArray.get(key));
          sparseArray.remove(key);
          i=-1;
    }
}
Sackurise
quelle
2
Das i = -1; am Ende macht nichts. Es gibt auch eine Methode, .clear()die bevorzugt werden sollte.
Paul Woitaschek
Warum sollten Sie eine for () - Schleife anstelle einer while () verwenden? Was Sie tun, macht keinen Sinn für eine Schleife
Phil A
Ich gehe davon aus, dass Sackurise schreiben wollte, um i-=1;das jetzt fehlende Element zu berücksichtigen. Es ist jedoch besser, die Schleife zurückzusetzen : for(int i=sparseArray.size()-1; i>=0; i++){...; oderwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
am
Referenzen wie "die obige Schleife" machen überhaupt keinen Sinn.
Der unglaubliche
Ich dachte, der Punkt eines "Iterators" sei die sichere Entfernung von Objekten. Ich habe keine Beispiele für die Iterator-Klasse mit sparseArrays gesehen, wie es sie für Hashmaps gibt. Dies kommt der sicheren Entfernung von Objekten am nächsten. Ich hoffe, es funktioniert ohne gleichzeitige Änderungsausnahmen.
Androidcoder
5

Hier ist einfach Iterator<T>und Iterable<T>Implementierungen für SparseArray<T>:

public class SparseArrayIterator<T> implements Iterator<T> {
    private final SparseArray<T> array;
    private int index;

    public SparseArrayIterator(SparseArray<T> array) {
        this.array = array;
    }

    @Override
    public boolean hasNext() {
        return array.size() > index;
    }

    @Override
    public T next() {
        return array.valueAt(index++);
    }

    @Override
    public void remove() {
        array.removeAt(index);
    }

}

public class SparseArrayIterable<T> implements Iterable<T> {
    private final SparseArray<T> sparseArray;

    public SparseArrayIterable(SparseArray<T> sparseArray) {
        this.sparseArray = sparseArray;
    }

    @Override
    public Iterator<T> iterator() {
        return new SparseArrayIterator<>(sparseArray);
    }
}

Wenn Sie nicht nur einen Wert, sondern auch einen Schlüssel iterieren möchten:

public class SparseKeyValue<T> {
    private final int key;
    private final T value;

    public SparseKeyValue(int key, T value) {
        this.key = key;
        this.value = value;
    }

    public int getKey() {
        return key;
    }

    public T getValue() {
        return value;
    }
}

public class SparseArrayKeyValueIterator<T> implements Iterator<SparseKeyValue<T>> {
    private final SparseArray<T> array;
    private int index;

    public SparseArrayKeyValueIterator(SparseArray<T> array) {
        this.array = array;
    }

    @Override
    public boolean hasNext() {
        return array.size() > index;
    }

    @Override
    public SparseKeyValue<T> next() {
        SparseKeyValue<T> keyValue = new SparseKeyValue<>(array.keyAt(index), array.valueAt(index));
        index++;
        return keyValue;
    }

    @Override
    public void remove() {
        array.removeAt(index);
    }

}

public class SparseArrayKeyValueIterable<T> implements Iterable<SparseKeyValue<T>> {
    private final SparseArray<T> sparseArray;

    public SparseArrayKeyValueIterable(SparseArray<T> sparseArray) {
        this.sparseArray = sparseArray;
    }

    @Override
    public Iterator<SparseKeyValue<T>> iterator() {
        return new SparseArrayKeyValueIterator<T>(sparseArray);
    }
}

Es ist nützlich, Dienstprogrammmethoden zu erstellen, die Folgendes zurückgeben Iterable<T>und Iterable<SparseKeyValue<T>>:

public abstract class SparseArrayUtils {
    public static <T> Iterable<SparseKeyValue<T>> keyValueIterable(SparseArray<T> sparseArray) {
        return new SparseArrayKeyValueIterable<>(sparseArray);
    }

    public static <T> Iterable<T> iterable(SparseArray<T> sparseArray) {
        return new SparseArrayIterable<>(sparseArray);
    }
}

Jetzt können Sie iterieren SparseArray<T>:

SparseArray<String> a = ...;

for (String s: SparseArrayUtils.iterable(a)) {
   // ...
}

for (SparseKeyValue<String> s: SparseArrayUtils.keyValueIterable(a)) {
  // ...
}
mischen
quelle
4

Wenn Sie Kotlin verwenden, können Sie Erweiterungsfunktionen als solche verwenden, zum Beispiel:

fun <T> LongSparseArray<T>.valuesIterator(): Iterator<T> {
    val nSize = this.size()
    return object : Iterator<T> {
        var i = 0
        override fun hasNext(): Boolean = i < nSize
        override fun next(): T = valueAt(i++)
    }
}

fun <T> LongSparseArray<T>.keysIterator(): Iterator<Long> {
    val nSize = this.size()
    return object : Iterator<Long> {
        var i = 0
        override fun hasNext(): Boolean = i < nSize
        override fun next(): Long = keyAt(i++)
    }
}

fun <T> LongSparseArray<T>.entriesIterator(): Iterator<Pair<Long, T>> {
    val nSize = this.size()
    return object : Iterator<Pair<Long, T>> {
        var i = 0
        override fun hasNext(): Boolean = i < nSize
        override fun next() = Pair(keyAt(i), valueAt(i++))
    }
}

Wenn Sie möchten, können Sie auch in eine Liste konvertieren. Beispiel:

sparseArray.keysIterator().asSequence().toList()

Ich denke , es könnte sogar zu löschen Gegenstände sicher sein , mit removeauf der LongSparseArrayselbst (nicht auf dem Iterator), wie sie in aufsteigender Reihenfolge ist.


BEARBEITEN: Es scheint noch einfacher zu sein, mit collection-ktx (Beispiel hier) ). Es ist sehr ähnlich implementiert wie das, was ich tatsächlich geschrieben habe.

Gradle benötigt dies:

implementation 'androidx.core:core-ktx:#'
implementation 'androidx.collection:collection-ktx:#'

Hier ist die Verwendung für LongSparseArray:

    val sparse= LongSparseArray<String>()
    for (key in sparse.keyIterator()) {
    }
    for (value in sparse.valueIterator()) {
    }
    sparse.forEach { key, value -> 
    }

Und für diejenigen , die Java verwenden, können Sie verwenden LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratorund LongSparseArrayKt.forEachzum Beispiel. Gleiches gilt für die anderen Fälle.

Android-Entwickler
quelle
-5

Die Antwort ist nein, weil SparseArrayes nicht liefert. Wie pstgesagt, dieses Ding bietet keine Schnittstellen.

Sie könnten 0 - size()Werte, die zurückgeben null, durchlaufen und überspringen , aber das ist es auch.

Wie ich in meinem Kommentar feststelle, verwenden Sie a Mapanstelle von a , wenn Sie iterieren müssen SparseArray. Verwenden Sie beispielsweise a, TreeMapdas in der Reihenfolge des Schlüssels iteriert.

TreeMap<Integer, MyType>
John B.
quelle
-6

Die akzeptierte Antwort enthält einige Lücken. Das Schöne am SparseArray ist, dass es Lücken in den Unanständigkeiten zulässt. Wir könnten also zwei Karten wie diese in einem SparseArray haben ...

(0,true)
(250,true)

Beachten Sie, dass die Größe hier 2 wäre. Wenn wir über die Größe iterieren, erhalten wir nur Werte für die Werte, die Index 0 und Index 1 zugeordnet sind. Auf die Zuordnung mit einem Schlüssel von 250 wird also nicht zugegriffen.

for(int i = 0; i < sparseArray.size(); i++) {
   int key = sparseArray.keyAt(i);
   // get the object by the key.
   Object obj = sparseArray.get(key);
}

Der beste Weg, dies zu tun, besteht darin, über die Größe Ihres Datensatzes zu iterieren und diese Unabhängigkeiten dann mit einem get () im Array zu überprüfen. Hier ist ein Beispiel mit einem Adapter, bei dem ich das Batch-Löschen von Elementen erlaube.

for (int index = 0; index < mAdapter.getItemCount(); index++) {
     if (toDelete.get(index) == true) {
        long idOfItemToDelete = (allItems.get(index).getId());
        mDbManager.markItemForDeletion(idOfItemToDelete);
        }
    }

Ich denke, im Idealfall hätte die SparseArray-Familie eine getKeys () -Methode, aber leider nicht.

Tyler Pfaff
quelle
4
Sie liegen falsch - die keyAtMethode gibt den Wert des n-ten Schlüssels zurück (in Ihrem Beispiel keyAt(1)würde zurückgegeben 250), nicht zu verwechseln mit getdem Wert des Elements, auf das der Schlüssel verweist.
Eborbob
Ich bin mir nicht sicher, was das "Dies" in Ihrem Kommentar ist. Geben Sie zu, dass Ihre Antwort falsch ist, oder sagen Sie, dass mein Kommentar falsch ist? Wenn letzteres der
Fall ist,
17
Meine Antwort ist falsch, ich werde sie nicht löschen, damit andere lernen können.
Tyler Pfaff