Come iterare tramite SparseArray?


311

Esiste un modo per scorrere su Java SparseArray (per Android)? Ero solito sparsearrayottenere facilmente i valori per indice. Non riuscivo a trovarne uno.


30
Wow, parla di una classe completamente non amata , conforme alle interfacce di raccolta ZERO ...

1
È possibile utilizzare un TreeMap<Integer, MyType>metodo che consenta di eseguire l'iterazione in ordine di chiave. Come detto, SparseArray è progettato per essere più efficiente di una HashMap, ma non consente l'iterazione.
Giovanni B,

2
è molto, molto improbabile, che le prestazioni dell'impl della mappa scelta siano il collo di bottiglia nella tua app.
Jeffrey Blattman,

3
@JeffreyBlattman non significa che dovremmo evitare di usare la struttura giusta quando è chiaramente appropriato.
gelido meraviglioso,

1
@frostymarvelous dice che è DUE VOLTE più veloce, probabilmente significa un risparmio di meno di 10ms. 10ms è rilevante nello schema più grande dell'app? Vale la pena usare un'interfaccia non ottimale che è più difficile da capire e mantenere? Non conosco la risposta a queste cose, ma la risposta non è "usa assolutamente l'array sparse a prescindere".
Jeffrey Blattman,

Risposte:


537

Sembra che abbia trovato la soluzione. Non avevo notato bene la keyAt(index)funzione.

Quindi andrò con qualcosa del genere:

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

25
la documentazione afferma che "keyAt (int index) Dato un indice nell'intervallo 0 ... size () - 1, restituisce la chiave dalla mappatura dei valori-chiave dell'indice che memorizza questo SparseArray." quindi funziona bene anche per il caso da te descritto.
Ruzanna

12
è meglio pre-calcolare la dimensione dell'array e utilizzare il valore costante nel loop.
Dmitry Zaytsev,

25
Non sarebbe più facile usare direttamente valueAt la funzione qui?
Milan Krstic,

34
Funzionerebbe anche all'interno del ciclo:Object obj = sparseArray.valueAt(i);
Florian,

27
valueAt(i)è più veloce di get(key), perché valueAt(i)e keyAt(i)sono entrambi O (1) , ma get(key)è O (log2 n) , quindi utilizzerei sicuramente sempre valueAt.
Mecki,

180

Se non ti interessano le chiavi, valueAt(int)puoi usarle durante l'iterazione attraverso l'array sparse per accedere direttamente ai valori.

for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
    Object obj = sparseArray.valueAt(i);
}

7
L'uso di valueAt () è utile (e più veloce della soluzione accettata) se la tua iterazione non si preoccupa delle chiavi, ovvero: un ciclo che conta le occorrenze di un valore specifico.
Sogger

2
Prendi sparseArray.size()in una variabile in modo che non chiamerà size()ogni volta.
Pratik Butani,

4
È ridondante copiare size () in una variabile. Facile da verificare se si guarda semplicemente il codice del metodo size (). Non riesco a capire perché non l'hai fatto prima di suggerire cose del genere ... Ricordo una volta 20 anni fa in cui avevamo semplici elenchi collegati che dovevano davvero contare le loro dimensioni ogni volta che te lo chiedevi, ma non credo che cose del genere esistono ancora ...
L'incredibile

Questo è garantito per essere nell'ordine chiave?
HughHughTeotl,

18

Ooor hai appena creato il tuo 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();
    }
}
}

9
IMHO sembra un po 'troppo ingegneristico. È fantastico però
hrules6872 il

12

Semplice come la torta. Assicurati solo di recuperare le dimensioni dell'array prima di eseguire effettivamente il ciclo.

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

Spero che questo ti aiuti.


11

Per chiunque stia usando Kotlin, onestamente il modo di gran lunga più semplice per scorrere su SparseArray è: usare l'estensione Kotlin di Anko o Android KTX ! (merito a Yazazzello per aver segnalato Android KTX)

Chiama semplicemente forEach { i, item -> }


sì, in realtà hai ragione. mio male, ho guardato i tag e ho pensato che Kotlin non dovrebbe essere qui. Ma ora ripensandoci che questa risposta è un buon riferimento allo stesso Kotlin. Anche se invece di usare Anko ti consiglierei di usare android.github.io/android-ktx/core-ktx (se potessi gentilmente modificare la tua risposta e aggiungere android-ktx la voterò)
Yazazzello

@Yazazzello hey non sapevo nemmeno di Android KTX, buon punto!
0101100101,

7

Per la rimozione di tutti gli elementi SparseArraydall'uso dei cicli di cui sopra porta a Exception.

Per evitare ciò, seguire il codice seguente per rimuovere tutti gli elementi SparseArraydall'uso di normali loop

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

2
I = -1; alla fine non fa nulla. Inoltre c'è un metodo chiamato .clear()che dovrebbe essere favorito.
Paul Woitaschek,

Perché dovresti usare un ciclo for () invece di un po '()? Quello che stai facendo non ha senso per il looping
Phil A

presumo che Sackurise volesse scrivere i-=1;per spiegare l'elemento ora mancante. Ma è meglio ripristinare il ciclo: for(int i=sparseArray.size()-1; i>=0; i++){...; owhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
THS

Riferimenti come "il ciclo precedente" non hanno alcun senso.
L'incredibile

Pensavo che il punto di un "iteratore" fosse la rimozione sicura degli oggetti. Non ho visto alcun esempio della classe Iterator con sparseArrays come esiste per gli hashmaps. Questo si avvicina di più alla rimozione sicura degli oggetti, spero che funzioni senza eccezioni di modifica simultanee.
Androidcoder

5

Ecco semplici Iterator<T>e Iterable<T>implementazioni per 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);
    }
}

Se vuoi iterare non solo un valore ma anche una chiave:

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

È utile creare metodi di utilità che restituiscono Iterable<T>e 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);
    }
}

Ora puoi iterare SparseArray<T>:

SparseArray<String> a = ...;

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

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

4

Se usi Kotlin, puoi usare le funzioni di estensione come tali, ad esempio:

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

Se lo desideri, puoi anche convertirlo in un elenco. Esempio:

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

Penso che potrebbe anche essere sicuro eliminare gli elementi usando removesu LongSparseArrayse stesso (non sull'iteratore), poiché è in ordine crescente.


EDIT: Sembra che ci sia anche un modo più semplice, usando collection-ktx (esempio qui ). È implementato in modo molto simile a quello che ho scritto, in realtà.

Gradle richiede questo:

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

Ecco l'uso per LongSparseArray:

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

E per coloro che utilizzano Java, è possibile utilizzare LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratore LongSparseArrayKt.forEach, per esempio. Lo stesso per gli altri casi.


-5

La risposta è no perché SparseArraynon lo fornisce. Come pstdetto, questa cosa non fornisce alcuna interfaccia.

È possibile eseguire il loop 0 - size()e saltare i valori che restituiscono null, ma questo è tutto.

Come dichiaro nel mio commento, se hai bisogno di iterare usa a Mapinvece di a SparseArray. Ad esempio, utilizzare un TreeMapche scorre in ordine dalla chiave.

TreeMap<Integer, MyType>

-6

La risposta accettata presenta alcuni buchi. La bellezza di SparseArray è che consente lacune negli indeci. Quindi, potremmo avere due mappe così, in uno SparseArray ...

(0,true)
(250,true)

Si noti che la dimensione qui sarebbe 2. Se si esegue l'iterazione sulla dimensione, otterremo solo i valori per i valori mappati sull'indice 0 e sull'indice 1. Quindi non è possibile accedere alla mappatura con una chiave di 250.

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

Il modo migliore per farlo è quello di scorrere le dimensioni del set di dati, quindi controllare quegli indici con un get () sull'array. Ecco un esempio con un adattatore in cui consento l'eliminazione batch di articoli.

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

Penso che idealmente la famiglia SparseArray avrebbe un metodo getKeys (), ma purtroppo no.


4
Ti sbagli: il keyAtmetodo restituisce il valore dell'ennesima chiave (nel tuo esempio keyAt(1)restituisce 250), da non confondere con il getquale restituisce il valore dell'elemento a cui fa riferimento la chiave.
Eborbob,

Non sono sicuro di cosa sia questo nel tuo commento. Stai ammettendo che la tua risposta è sbagliata o stai dicendo che il mio commento è sbagliato? Se quest'ultimo controlla prego developer.android.com/reference/android/util/…
Eborbob

17
La mia risposta è sbagliata, non la cancellerò in modo che altri possano imparare.
Tyler Pfaff,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.