Jak iterować przez SparseArray?

311

Czy istnieje sposób na iterację w Javie SparseArray (dla Androida)? Kiedyś sparsearrayłatwo uzyskiwałem wartości według indeksu. Nie mogłem znaleźć.

Ruzanna
źródło
30
Wow, rozmawiaj o całkowicie niekochanej klasie , jest zgodna z interfejsami kolekcji ZERO ...
1
Możesz użyć narzędzia, TreeMap<Integer, MyType>które pozwala na iterację w kolejności według klucza. Jak wspomniano, SparseArray został zaprojektowany tak, aby był bardziej wydajny niż HashMap, ale nie pozwala na iterację.
John B,
2
bardzo, bardzo mało prawdopodobne jest, że wydajność wybranej mapy będzie wąskim gardłem w Twojej aplikacji.
Jeffrey Blattman
3
@JeffreyBlattman nie oznacza, że ​​powinniśmy unikać używania właściwej struktury, gdy jest to wyraźnie właściwe.
frostymarvellous
1
@frostymarvellous mówi, że jest dwa razy szybszy, co prawdopodobnie oznacza oszczędność poniżej 10 ms. Czy 10ms ma znaczenie w większym programie? Czy warto używać nieoptymalnego interfejsu, który jest trudniejszy do zrozumienia i utrzymania? Nie znam odpowiedzi na te pytania, ale odpowiedź nie brzmi „absolutnie używaj rzadkiej tablicy niezależnie”.
Jeffrey Blattman

Odpowiedzi:

536

Wygląda na to, że znalazłem rozwiązanie. Nie zauważyłem poprawnie tej keyAt(index)funkcji.

Więc pójdę z czymś takim:

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
źródło
25
dokumentacja stwierdza, że ​​„keyAt (int index) Biorąc pod uwagę indeks z zakresu 0 ... size () - 1, zwraca klucz z indeksu mapowania klucz-wartość przechowywanego w tym SparseArray”. więc działa dobrze dla mnie nawet w przypadku opisanym przez ciebie.
Ruzanna,
12
lepiej wstępnie obliczyć rozmiar tablicy i użyć stałej wartości w pętli.
Dmitrij Zajcew
25
Czy nie byłoby łatwiej korzystać z funkcji directAtate tutaj?
Milan Krstic,
34
To również działałoby w pętli:Object obj = sparseArray.valueAt(i);
Florian
27
valueAt(i)jest szybszy niż get(key), ponieważ valueAt(i)i keyAt(i)oba są O (1) , ale get(key)jest O (log2 n) , więc na pewno zawsze użyję valueAt.
Mecki
180

Jeśli nie przejmujesz się kluczami, valueAt(int)możesz to wykorzystać podczas iteracji przez rzadką tablicę, aby uzyskać bezpośredni dostęp do wartości.

for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
    Object obj = sparseArray.valueAt(i);
}
Pogo Lin
źródło
7
Korzystanie z valueAt () jest użyteczne (i szybsze niż zaakceptowane rozwiązanie), jeśli w twojej iteracji nie zależy na kluczach, tj .: wystąpieniach zliczania pętli o określonej wartości.
Sogger
2
Weź sparseArray.size()jedną zmienną, aby nie zadzwoniła za size()każdym razem.
Pratik Butani
4
Nadmiarowe jest kopiowanie size () do zmiennej. Łatwo to sprawdzić, jeśli spojrzysz na kod metody size (). Nie rozumiem, dlaczego nie powiedziałeś, zanim zasugerowałeś takie rzeczy ... Pamiętam czas 20 lat temu, kiedy mieliśmy proste połączone listy, które naprawdę musiały liczyć ich wielkość za każdym razem, gdy o to prosiłeś, ale nie wierzę że takie rzeczy wciąż istnieją ...
Niesamowity
Czy jest to gwarantowane w kolejności kluczowej?
HughHughTeotl
18

Ooor, po prostu tworzysz swój własny 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
źródło
9
IMHO wydaje się to trochę przesadzone. To niesamowite
hrules6872,
12

Proste jak ciasto. Upewnij się, że pobierasz rozmiar tablicy przed wykonaniem pętli.

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

Mam nadzieję że to pomoże.

Pascal
źródło
11

Dla każdego, kto używa Kotlin, najprostszym sposobem na iterację SparseArray jest: Użyj rozszerzenia Kotlin z Anko lub Androida KTX ! (podziękowania dla Yazazzello za wskazanie Androida KTX)

Po prostu zadzwoń forEach { i, item -> }

0101100101
źródło
tak, masz rację. mój zły, spojrzałem na metki i pomyślałem, że Kotlina nie powinno tu być. Ale teraz zastanawiając się, czy ta odpowiedź jest dobrym odniesieniem do samej Kotliny. Chociaż zamiast korzystać z Anko zalecałbym użycie android.github.io/android-ktx/core-ktx (jeśli możesz uprzejmie edytować swoją odpowiedź i dodać android-ktx, to ją głosuję)
Yazazzello
@ Yazazzello hej, nawet nie wiedziałem o Android KTX, dobra uwaga!
0101100101
7

Do usunięcia wszystkich elementów z SparseArrayużycia powyższych pętli prowadzi do Exception.

Aby tego uniknąć Postępuj zgodnie z poniższym kodem, aby usunąć wszystkie elementy z SparseArraynormalnych pętli

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
źródło
2
I = -1; na końcu nic nie robi. Istnieje również metoda o nazwie, .clear()która powinna być preferowana.
Paul Woitaschek
Dlaczego miałbyś używać pętli for () zamiast while ()? To, co robisz, nie ma sensu zapętlać
Phil A
Zakładam, że Sackurise chciał napisać, i-=1;aby uwzględnić brakujący element. Ale to lepiej, aby przywrócić pętlę: for(int i=sparseArray.size()-1; i>=0; i++){...; lubwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
ths
Odnośniki takie jak „powyższa pętla” nie mają żadnego sensu.
Niesamowity
Myślałem, że celem „iteratora” jest bezpieczne usuwanie obiektów. Nie widziałem żadnych przykładów klasy Iterator z rzadkimi tablicami, tak jak w przypadku map skrótów. Jest to najbliższe rozwiązaniu problemu bezpiecznego usuwania obiektów, mam nadzieję, że zadziała bez jednoczesnych wyjątków modyfikacji.
Androidcoder
5

Oto proste Iterator<T>i Iterable<T>implementacje dla 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);
    }
}

Jeśli chcesz iterować nie tylko wartość, ale także klucz:

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

Przydatne jest tworzenie zwracających się metod narzędziowych Iterable<T>i 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);
    }
}

Teraz możesz iterować SparseArray<T>:

SparseArray<String> a = ...;

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

for (SparseKeyValue<String> s: SparseArrayUtils.keyValueIterable(a)) {
  // ...
}
mixel
źródło
4

Jeśli korzystasz z Kotlin, możesz używać funkcji rozszerzeń jako takich, na przykład:

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++))
    }
}

Możesz także przekonwertować na listę, jeśli chcesz. Przykład:

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

Myślę, że może to być nawet bezpieczne usuwanie elementów za pomocą removena LongSparseArraysiebie (nie na iterator), jak to jest w porządku rosnącym.


EDYCJA: Wydaje się, że istnieje jeszcze łatwiejszy sposób, używając collection-ktx (przykład tutaj ). Zaimplementowano go bardzo podobnie do tego, co napisałem.

Gradle wymaga tego:

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

Oto zastosowanie LongSparseArray:

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

A dla tych, którzy używają Java, możesz użyć LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratori LongSparseArrayKt.forEachna przykład. To samo dotyczy innych przypadków.

programista Androida
źródło
-5

Odpowiedź brzmi „nie”, ponieważ SparseArraynie zawiera odpowiedzi. Tak jakpst to ujęto, ta rzecz nie zapewnia żadnych interfejsów.

Możesz zapętlać 0 - size()i pomijać zwracane wartościnull , ale o to chodzi.

Jak stwierdzam w moim komentarzu, jeśli chcesz iterować, użyj Mapzamiast zamiast SparseArray. Na przykład użyj TreeMapiteracji w kolejności według klucza.

TreeMap<Integer, MyType>
John B.
źródło
-6

W zaakceptowanej odpowiedzi są pewne dziury. Piękno SparseArray polega na tym, że pozwala na luki w indkach. Moglibyśmy mieć dwie takie mapy w SparseArray ...

(0,true)
(250,true)

Zauważ, że tutaj rozmiar wynosiłby 2. Jeśli iterujemy ponad rozmiarem, otrzymamy tylko wartości dla wartości mapowanych na indeks 0 i indeks 1. Zatem mapowanie za pomocą klucza 250 nie jest dostępne.

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

Najlepszym sposobem na to jest iteracja wielkości zbioru danych, a następnie sprawdzenie tych indek za pomocą get () w tablicy. Oto przykład z adapterem, w którym zezwalam na grupowe usuwanie elementów.

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

Myślę, że idealnie rodzina SparseArray miałaby metodę getKeys (), ale niestety nie.

Tyler Pfaff
źródło
4
Mylisz się - keyAtmetoda zwraca wartość n-tego klucza (w twoim przykładzie keyAt(1)powróciłoby 250), nie mylić z getktórą zwraca wartość elementu, do którego odwołuje się klucz.
Eborbob,
Nie jestem pewien, co to jest w twoim komentarzu. Czy przyznajesz, że twoja odpowiedź jest błędna, czy twierdzisz, że mój komentarz jest błędny? Jeśli to ostatnie, sprawdź developer.android.com/reference/android/util/…
Eborbob
17
Moja odpowiedź jest błędna, nie usunę jej, aby inni mogli się uczyć.
Tyler Pfaff