Comment itérer sur SparseArray?


311

Existe-t-il un moyen d'itérer sur Java SparseArray (pour Android)? J'obtenais sparsearrayfacilement des valeurs par index. Je n'ai pas pu en trouver un.


30
Wow, parlons d'une classe complètement mal aimée , conforme aux interfaces de collection ZERO ...

1
Vous pouvez utiliser un TreeMap<Integer, MyType>qui vous permettrait d'itérer dans l'ordre par clé. Comme indiqué, SparseArray est conçu pour être plus efficace qu'un HashMap, mais il ne permet pas l'itération.
John B

2
il est très, très peu probable que les performances de l'impl de carte que vous choisissez soient le goulot d'étranglement de votre application.
Jeffrey Blattman

3
@JeffreyBlattman ne signifie pas que nous devons éviter d'utiliser la bonne structure lorsqu'elle est clairement appropriée.
frostymarvelous

1
@frostymarvelous dit que c'est deux fois plus rapide, ce qui signifie probablement une économie de moins de 10 ms. 10 ms est-il pertinent dans le schéma plus grand de l'application? Vaut-il la peine d'utiliser une interface sous-optimale plus difficile à comprendre et à maintenir? Je ne connais pas la réponse à ces choses, mais la réponse n'est pas "absolument utiliser un tableau clairsemé indépendamment".
Jeffrey Blattman

Réponses:


537

Il semble que j'ai trouvé la solution. Je n'avais pas bien remarqué la keyAt(index)fonction.

Je vais donc aller avec quelque chose comme ça:

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 documentation indique que "keyAt (int index) Étant donné un index dans la plage 0 ... size () - 1, renvoie la clé du mappage de valeur-clé indexth que ce SparseArray stocke." donc cela fonctionne bien pour moi même pour le cas que vous décrivez.
Ruzanna

12
il est préférable de précalculer la taille du tableau et d'utiliser une valeur constante en boucle.
Dmitry Zaytsev

25
Ne serait-il pas plus facile d'utiliser directement la fonction valueAt ici?
Milan Krstic

34
Cela fonctionnerait aussi à l'intérieur de la boucle:Object obj = sparseArray.valueAt(i);
Florian

27
valueAt(i)est plus rapide que get(key), parce que valueAt(i)et keyAt(i)sont tous les deux O (1) , mais get(key)est O (log2 n) , donc j'utiliserais certainement toujours valueAt.
Mecki

180

Si vous ne vous souciez pas des clés, vous valueAt(int)pouvez les utiliser lors de l'itération à travers le tableau clairsemé pour accéder directement aux valeurs.

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

7
L'utilisation de valueAt () est utile (et plus rapide que la solution acceptée) si votre itération ne se soucie pas des clés, c'est-à-dire: une boucle comptant les occurrences d'une valeur spécifique.
Sogger

2
Prenez sparseArray.size()une variable pour qu'elle n'appelle pas à size()chaque fois.
Pratik Butani,

4
Il est redondant de copier size () dans une variable. Facile à vérifier si vous regardez simplement le code de la méthode size (). Je ne comprends pas pourquoi vous ne l'avez pas fait avant de suggérer de telles choses ... Je me souviens, il y a 20 ans, où nous avions de simples listes chaînées qui devaient vraiment compter leur taille chaque fois que vous leur en demandiez mais je ne crois pas que de telles choses existent toujours ...
L'incroyable

Est-ce que cela est garanti dans l'ordre clé?
HughHughTeotl

18

Ooor vous venez de créer votre propre 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
À mon humble avis, il semble un peu trop d'ingénierie. C'est génial
hrules6872

12

Simple comme tarte. Assurez-vous simplement de récupérer la taille du tableau avant d' effectuer la boucle.

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

J'espère que cela t'aides.


11

Pour ceux qui utilisent Kotlin, honnêtement, la façon de loin la plus simple de parcourir un SparseArray est: Utilisez l'extension Kotlin d' Anko ou Android KTX ! (crédit à Yazazzello pour avoir souligné Android KTX)

Appelez simplement forEach { i, item -> }


oui, vous avez en fait raison. mon mauvais, j'ai regardé les étiquettes et j'ai pensé que Kotlin ne devrait pas être ici. Mais ayant maintenant une seconde pensée que cette réponse est une bonne référence à Kotlin lui-même. Bien qu'au lieu d'utiliser Anko, je recommande d'utiliser android.github.io/android-ktx/core-ktx (si vous pouviez bien vouloir modifier votre réponse et ajouter android-ktx, je le voterais positivement)
Yazazzello

@Yazazzello hé je ne connaissais même pas Android KTX, bon point!
0101100101

7

Pour supprimer tous les éléments de l' SparseArrayutilisation des boucles ci-dessus,Exception .

Pour éviter cela Suivez le code ci-dessous pour supprimer tous les éléments de l' SparseArrayutilisation des boucles 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;
    }
}

2
Le i = -1; à la fin ne fait rien. Il existe également une méthode appelée .clear()qui devrait être privilégiée.
Paul Woitaschek

Pourquoi utiliseriez-vous une boucle for () au lieu d'un while ()? Ce que vous faites n'a aucun sens pour la boucle
Phil A

je suppose que Sackurise voulait écrire i-=1;pour tenir compte de l'élément maintenant manquant. Mais il vaut mieux inverser la boucle for(int i=sparseArray.size()-1; i>=0; i++){...:; ouwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
le

Des références comme "le bouclage ci-dessus" n'ont aucun sens.
L'incroyable

Je pensais que le but d'un «itérateur» était de retirer des objets en toute sécurité. Je n'ai vu aucun exemple de la classe Iterator avec sparseArrays comme il y en a pour les hashmaps. Cela se rapproche le plus de la suppression sûre des objets, j'espère que cela fonctionne sans exceptions de modification simultanées.
Androidcoder

5

Voici des implémentations simples Iterator<T>et Iterable<T>pour 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 vous souhaitez itérer non seulement une valeur mais également une clé:

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

Il est utile de créer des méthodes utilitaires qui renvoient Iterable<T>et 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);
    }
}

Vous pouvez maintenant répéter SparseArray<T>:

SparseArray<String> a = ...;

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

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

4

Si vous utilisez Kotlin, vous pouvez utiliser les fonctions d'extension en tant que telles, par exemple:

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

Vous pouvez également convertir en liste, si vous le souhaitez. Exemple:

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

Je pense qu'il pourrait même être sûr de supprimer des éléments en utilisant removesur LongSparseArraylui - même (pas sur l'itérateur), car il est dans l'ordre croissant.


EDIT: Il semble qu'il existe un moyen encore plus simple, en utilisant collection-ktx (exemple ici ). Il est mis en œuvre de manière très similaire à ce que j'ai écrit, en fait.

Gradle exige ceci:

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

Voici l'utilisation de LongSparseArray:

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

Et pour ceux qui utilisent Java, vous pouvez utiliser LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratoret LongSparseArrayKt.forEach, par exemple. Idem pour les autres cas.


-5

La réponse est non car SparseArrayne la fournit pas. Comme pstdit, cette chose ne fournit aucune interface.

Vous pouvez boucler 0 - size()et ignorer les valeurs qui retournentnull , mais c'est à peu près tout.

Comme je l'indique dans mon commentaire, si vous avez besoin d'itérer, utilisez un Mapau lieu d'un SparseArray. Par exemple, utilisez un TreeMapqui itère dans l'ordre par la clé.

TreeMap<Integer, MyType>

-6

La réponse acceptée comporte quelques trous. La beauté du SparseArray est qu'il permet des lacunes dans les indéces. Donc, nous pourrions avoir deux cartes comme ça, dans un SparseArray ...

(0,true)
(250,true)

Notez que la taille serait ici de 2. Si nous itérons sur la taille, nous n'obtiendrons que les valeurs des valeurs mappées à l'index 0 et à l'index 1. Ainsi, le mappage avec une clé de 250 n'est pas accessible.

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 meilleure façon de le faire est d'itérer sur la taille de votre ensemble de données, puis de vérifier ces indéces avec un get () sur le tableau. Voici un exemple avec un adaptateur où j'autorise la suppression par lots d'éléments.

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

Je pense que l'idéal serait que la famille SparseArray ait une méthode getKeys (), mais hélas non.


4
Vous avez tort - la keyAtméthode retourne la valeur de la nième clé (dans votre exemple keyAt(1)retournerait 250), à ne pas confondre avec celle getqui retourne la valeur de l'élément référencé par la clé.
Eborbob

Je ne suis pas sûr de savoir ce que cela signifie dans votre commentaire. Admettez-vous que votre réponse est fausse ou dites-vous que mon commentaire est faux? Si ce dernier, veuillez vérifier developer.android.com/reference/android/util/…
Eborbob

17
Ma réponse est fausse, je ne la supprimerai pas pour que les autres puissent apprendre.
Tyler Pfaff
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.