Como iterar através do SparseArray?

311

Existe uma maneira de interagir com o Java SparseArray (para Android)? Eu costumava sparsearrayobter facilmente valores por índice. Não consegui encontrar um.

Ruzanna
fonte
30
Uau, falar sobre uma classe completamente sem amor , está em conformidade com interfaces de Zero Collection ...
1
Você pode usar um TreeMap<Integer, MyType>que permita iterar em ordem por chave. Como afirmado, o SparseArray foi projetado para ser mais eficiente que um HashMap, mas não permite a iteração.
John B
2
é muito, muito improvável que o desempenho do mapa que você escolher seja o gargalo do seu aplicativo.
Jeffrey Blattman
3
@JeffreyBlattman não significa que devemos evitar o uso da estrutura correta quando for claramente apropriada.
Frostymarvelous
1
@frostymarvelous diz que é DUAS VEZES mais rápido, o que provavelmente significa uma economia de menos de 10ms. 10ms são relevantes no esquema mais amplo do aplicativo? Vale a pena usar uma interface abaixo do ideal, mais difícil de entender e manter? Eu não sei a resposta para essas coisas, mas a resposta não é "absolutamente usar matriz esparsa independentemente".
Jeffrey Blattman

Respostas:

537

Parece que encontrei a solução. Eu não tinha notado corretamente a keyAt(index)função.

Então, eu vou com algo assim:

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
fonte
25
a documentação afirma que "keyAt (int index) Dado um índice no intervalo 0 ... size () - 1, retorna a chave do mapeamento de valor-chave do indexth que esse SparseArray armazena." portanto, funciona bem para mim, mesmo no caso descrito por você.
Ruzanna
12
é melhor pré-calcular o tamanho da matriz e usar o valor constante no loop.
Dmitry Zaytsev
25
Não seria mais fácil usar diretamente a função valueAt aqui?
Milan Krstic
34
Isso também funcionaria dentro do loop:Object obj = sparseArray.valueAt(i);
Florian
27
valueAt(i)é mais rápido que get(key), porque valueAt(i)e keyAt(i)são ambos O (1) , mas get(key)é O (log2 n) , então eu certamente sempre usaria valueAt.
Mecki
180

Se você não se importa com as chaves, valueAt(int)pode usá-lo durante a iteração na matriz esparsa para acessar os valores diretamente.

for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
    Object obj = sparseArray.valueAt(i);
}
Pogo Lin
fonte
7
Usar valueAt () é útil (e mais rápido que a solução aceita) se sua iteração não se importar com as chaves, ou seja: um loop contando ocorrências de um valor específico.
Sogger 13/03
2
Pegue sparseArray.size()uma variável para que ela não seja chamada size()sempre.
Pratik Butani 26/10/16
4
Não é redundante copiar size () para uma variável. Fácil de verificar se você olha apenas o código do método size (). Eu não consigo entender por que você não fez isso antes de sugerir essas coisas ... Lembro-me de uma época, há 20 anos, em que tínhamos listas simples que realmente precisavam contar o tamanho toda vez que você pedia, mas eu não acredito que essas coisas ainda existem ...
The Incrible Jan
Isso está garantido em ordem-chave?
precisa saber é o seguinte
18

Ou você acabou de criar seu próprio 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
fonte
9
IMHO parece um pouco excesso de engenharia. É tho incrível
hrules6872
12

Simples como torta. Apenas certifique-se de buscar o tamanho da matriz antes de executar o loop.

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

Espero que isto ajude.

Pascal
fonte
11

Para quem está usando o Kotlin, honestamente, a maneira mais fácil de interagir com um SparseArray é: Use a extensão Kotlin da Anko ou Android KTX ! (agradecemos a Yazazzello por apontar o Android KTX)

Basta ligar forEach { i, item -> }

0101100101
fonte
Sim, você está realmente certo. meu mal, olhei as etiquetas e pensei que Kotlin não deveria estar aqui. Mas agora, pensando bem, que essa resposta é uma boa referência ao próprio Kotlin. Embora em vez de usar Anko eu recomendo usar android.github.io/android-ktx/core-ktx (se dignasse editar a sua resposta e adicionar android-KTX eu vou upvote-lo)
Yazazzello
@Yazazzello hey Eu nem sabia sobre o Android KTX, bom ponto!
0101100101
7

Para remover todos os elementos do SparseArrayuso do loop acima, leva a Exception.

Para evitar isso Siga o código abaixo para remover todos os elementos do SparseArrayuso de loops normais

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
fonte
2
O i = -1; no final não faz nada. Também existe um método chamado .clear()que deve ser favorecido.
Paul Woitaschek 26/10
Por que você usaria um loop for () em vez de um tempo ()? O que você está fazendo não faz sentido para executar um loop
Phil A
Presumo que Sackurise queria escrever i-=1;para dar conta do elemento que está faltando. Mas é melhor reverter o loop for(int i=sparseArray.size()-1; i>=0; i++){...:; ouwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
ths
Referências como "o loop acima" não fazem nenhum sentido.
O incrível Jan
Eu pensei que o objetivo de um 'iterador' era a remoção segura de objetos. Eu não vi nenhum exemplo da classe Iterator com sparseArrays como os hashmaps. Isso é o mais próximo de abordar a remoção segura de objetos, espero que funcione sem exceções de modificação simultâneas.
Androidcoder 31/03
5

Aqui é simples Iterator<T>e Iterable<T>implementações 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);
    }
}

Se você deseja iterar não apenas um valor, mas também uma chave:

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

É útil criar métodos utilitários que retornam 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);
    }
}

Agora você pode iterar SparseArray<T>:

SparseArray<String> a = ...;

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

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

Se você usa o Kotlin, pode usar as funções de extensão como tais, por exemplo:

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

Você também pode converter para uma lista, se desejar. Exemplo:

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

Eu acho que pode até ser seguro excluir itens usando removepor LongSparseArraysi só (não no iterador), pois está em ordem crescente.


EDIT: Parece que existe uma maneira ainda mais fácil, usando collection-ktx (exemplo aqui ). É implementado de uma maneira muito semelhante ao que escrevi, na verdade.

Gradle exige o seguinte:

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

Aqui está o uso do LongSparseArray:

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

E para aqueles que usam Java, você pode usar LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratore LongSparseArrayKt.forEach, por exemplo. O mesmo para os outros casos.

desenvolvedor android
fonte
-5

A resposta é não, porque SparseArraynão a fornece. Como pstdito, essa coisa não fornece nenhuma interface.

Você pode fazer um loop 0 - size()e pular os valores que retornam null, mas é isso.

Como afirmo no meu comentário, se você precisar repetir, use a em Mapvez de a SparseArray. Por exemplo, use um TreeMapque itere em ordem pela chave.

TreeMap<Integer, MyType>
John B
fonte
-6

A resposta aceita tem alguns buracos. A beleza do SparseArray é que ele permite lacunas nos indeces. Então, poderíamos ter dois mapas assim, em um SparseArray ...

(0,true)
(250,true)

Observe que o tamanho aqui seria 2. Se iterarmos sobre o tamanho, obteremos apenas valores para os valores mapeados para o índice 0 e o índice 1. Portanto, o mapeamento com uma chave de 250 não é acessado.

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

A melhor maneira de fazer isso é iterar sobre o tamanho do seu conjunto de dados e, em seguida, verificar esses indeces com um get () na matriz. Aqui está um exemplo com um adaptador no qual estou permitindo a exclusão de itens em lote.

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

Eu acho que idealmente a família SparseArray teria um método getKeys (), mas infelizmente não.

Tyler Pfaff
fonte
4
Você está errado - o keyAtmétodo retorna o valor da enésima chave (no seu exemplo keyAt(1)retornaria 250), para não ser confundido com o getque retorna o valor do elemento referenciado pela chave.
Eborbob
Não sei ao certo qual é o 'isso' no seu comentário. Você está admitindo que sua resposta está errada ou está dizendo que meu comentário está errado? Se este último, verifique developer.android.com/reference/android/util/…
Eborbob
17
Minha resposta está errada, não a excluirei para que outras pessoas possam aprender.
Tyler Pfaff 29/01