¿Cómo iterar a través de SparseArray?

311

¿Hay alguna forma de iterar sobre Java SparseArray (para Android)? Solía sparsearrayobtener valores fácilmente por índice. No pude encontrar uno.

Ruzanna
fuente
30
Wow, hablar de una clase completamente sin amor , se ajusta a las interfaces Zero Collection ...
1
Puede usar uno TreeMap<Integer, MyType>que le permita iterar en orden por clave. Como se indicó, SparseArray está diseñado para ser más eficiente que un HashMap, pero no permite la iteración.
John B
2
es muy, muy poco probable que el rendimiento del mapa implícito que elija sea el cuello de botella en su aplicación.
Jeffrey Blattman
3
@JeffreyBlattman no significa que debamos evitar usar la estructura correcta cuando sea claramente apropiada.
frostymarvelous
1
@frostymarvelous dice que es DOS VECES más rápido, eso probablemente signifique un ahorro de menos de 10 ms. ¿Son relevantes 10 ms en el esquema más amplio de la aplicación? ¿Vale la pena usar una interfaz subóptima que sea más difícil de entender y mantener? No sé la respuesta a esas cosas, pero la respuesta no es "absolutamente usar matriz dispersa independientemente".
Jeffrey Blattman

Respuestas:

537

Parece que encontré la solución. No había notado correctamente la keyAt(index)función.

Entonces iré con algo como esto:

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
fuente
25
la documentación indica que "keyAt (int index) Dado un índice en el rango 0 ... size () - 1, devuelve la clave de la asignación de clave-valor indexth que almacena este SparseArray". así que funciona bien para mí, incluso para el caso descrito por usted.
Ruzanna
12
Es mejor calcular previamente el tamaño de la matriz y usar un valor constante en el bucle.
Dmitry Zaytsev
25
¿No sería más fácil usar directamente la función valueAt aquí?
Milan Krstic
34
Esto también funcionaría dentro del circuito:Object obj = sparseArray.valueAt(i);
Florian
27
valueAt(i)es más rápido que get(key), porque valueAt(i)y keyAt(i)son O (1) , pero get(key)es O (log2 n) , por lo que ciertamente siempre lo usaría valueAt.
Mecki
180

Si no le importan las claves, valueAt(int)puede utilizarlas mientras realiza una iteración a través de la matriz dispersa para acceder a los valores directamente.

for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
    Object obj = sparseArray.valueAt(i);
}
Pogo Lin
fuente
77
El uso de valueAt () es útil (y más rápido que la solución aceptada) si su iteración no se preocupa por las claves, es decir: ocurre un recuento de bucles de un valor específico.
Sogger
2
Tome sparseArray.size()una variable para que no llame size()siempre.
Pratik Butani
44
Es redundante copiar size () a una variable. Fácil de verificar si solo mira el código del método size (). No puedo entender por qué no lo hiciste antes de sugerir esas cosas ... Recuerdo un momento hace 20 años en el que teníamos listas simples vinculadas que realmente tenían que contar su tamaño cada vez que las pedías, pero no creo que tales cosas todavía existen ...
El increíble Jan
¿Se garantiza que esto esté en el orden clave?
HughHughTeotl
18

O bien, simplemente cree su propio 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
fuente
99
En mi humilde opinión, parece un poco sobre ingeniería. Es impresionante aunque
hrules6872
12

Simple como pastel. Solo asegúrese de obtener el tamaño de la matriz antes de realizar el bucle.

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

Espero que esto ayude.

Pascal
fuente
11

Para quien esté usando Kotlin, honestamente, la forma más fácil de iterar sobre un SparseArray es: ¡Use la extensión Kotlin de Anko o Android KTX ! (crédito a Yazazzello por señalar Android KTX)

Simplemente llame forEach { i, item -> }

0101100101
fuente
Sí, en realidad tienes razón. mal, miré las etiquetas y pensé que Kotlin no debería estar aquí. Pero ahora teniendo un segundo pensamiento que esta respuesta es una buena referencia a Kotlin en sí. Aunque en lugar de usar Anko, recomendaría usar android.github.io/android-ktx/core-ktx (si pudieras editar tu respuesta y agregar android-ktx, la votaré)
Yazazzello
@Yazazzello oye, ni siquiera sabía sobre Android KTX, ¡buen punto!
0101100101
7

Para eliminar todos los elementos del SparseArrayuso de los cables de bucle anteriores a Exception.

Para evitar esto Siga el siguiente código para eliminar todos los elementos del SparseArrayuso de bucles normales

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
fuente
2
El i = -1; Al final no hace nada. También hay un método llamado .clear()que debe ser favorecido.
Paul Woitaschek
¿Por qué usarías un bucle for () en lugar de un while ()? Lo que estás haciendo no tiene sentido para bucle
Phil A
Supongo que Sackurise quería escribir i-=1;para tener en cuenta el elemento que ahora falta. Pero es mejor revertir el ciclo for(int i=sparseArray.size()-1; i>=0; i++){...:; owhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
THS
Referencias como "el bucle anterior" no tienen ningún sentido.
El increíble Jan
Pensé que el punto de un 'iterador' era la eliminación segura de objetos. No he visto ningún ejemplo de la clase Iterator con sparseArrays como hay para hashmaps. Esto se acerca más a abordar la eliminación segura de objetos, espero que funcione sin excepciones de modificaciones concurrentes.
Androidcoder
5

Aquí es simple Iterator<T>e Iterable<T>implementaciones para 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);
    }
}

Si desea iterar no solo un valor sino también una clave:

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 útil crear métodos de utilidad que devuelvan Iterable<T>y 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);
    }
}

Ahora puedes iterar SparseArray<T>:

SparseArray<String> a = ...;

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

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

Si usa Kotlin, puede usar funciones de extensión como tales, por ejemplo:

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

También puede convertir a una lista, si lo desea. Ejemplo:

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

Creo que incluso podría ser seguro eliminar elementos utilizando removeen LongSparseArraysí mismo (no en el iterador), ya que está en orden ascendente.


EDITAR: Parece que hay una manera aún más fácil, usando collection-ktx (ejemplo aquí ). Se implementa de una manera muy similar a lo que escribí, actally.

Gradle requiere esto:

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

Aquí está el uso de LongSparseArray:

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

Y para aquellos que usan Java, puede usar LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratory LongSparseArrayKt.forEach, por ejemplo. Lo mismo para los otros casos.

desarrollador de Android
fuente
-5

La respuesta es no porque SparseArrayno lo proporciona. En otras pstpalabras, esto no proporciona ninguna interfaz.

Puede recorrer 0 - size()y omitir valores que regresan null, pero eso es todo.

Como digo en mi comentario, si necesita iterar, use a en Maplugar de a SparseArray. Por ejemplo, use un TreeMapque itera en orden por la tecla.

TreeMap<Integer, MyType>
John B
fuente
-6

La respuesta aceptada tiene algunos agujeros. La belleza de SparseArray es que permite huecos en las indeces. Entonces, podríamos tener dos mapas así, en un SparseArray ...

(0,true)
(250,true)

Observe que el tamaño aquí sería 2. Si iteramos sobre el tamaño, solo obtendremos valores para los valores asignados al índice 0 y al índice 1. Por lo tanto, no se accede a la asignación con una clave de 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);
}

La mejor manera de hacer esto es iterar sobre el tamaño de su conjunto de datos, luego verificar esas indeces con un get () en la matriz. Aquí hay un ejemplo con un adaptador donde estoy permitiendo la eliminación por lotes de elementos.

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

Creo que idealmente la familia SparseArray tendría un método getKeys (), pero por desgracia no lo tiene.

Tyler Pfaff
fuente
44
Estás equivocado: el keyAtmétodo devuelve el valor de la enésima clave (en tu ejemplo keyAt(1)devolvería 250), que no debe confundirse con el getque devuelve el valor del elemento al que hace referencia la clave.
Eborbob
No estoy seguro de cuál es el "esto" en su comentario. ¿Estás admitiendo que tu respuesta es incorrecta o estás diciendo que mi comentario es incorrecto? Si es esto último, consulte developer.android.com/reference/android/util/…
Eborbob
17
Mi respuesta es incorrecta, no la eliminaré para que otros puedan aprender.
Tyler Pfaff