Existe uma maneira de interagir com o Java SparseArray (para Android)? Eu costumava sparsearrayobter facilmente valores por índice. Não consegui encontrar um.
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);}
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);}
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:
publicfinalclassSparseArrayIterator<E>implementsListIterator<E>{privatefinalSparseArray<E> array;privateint cursor;privateboolean 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)}.
*/publicstatic<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)}.
*/publicstatic<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)}.
*/publicstatic<E>ListIterator<E> iterateAt(SparseArray<E> array,int location){returnnewSparseArrayIterator<E>(array, location);}privateSparseArrayIterator(SparseArray<E> array,int location){this.array = array;if(location <0){
cursor =-1;
cursorNowhere =true;}elseif(location < array.size()){
cursor = location;
cursorNowhere =false;}else{
cursor = array.size()-1;
cursorNowhere =true;}}@Overridepublicboolean hasNext(){return cursor < array.size()-1;}@Overridepublicboolean hasPrevious(){return cursorNowhere && cursor >=0|| cursor >0;}@Overridepublicint nextIndex(){if(hasNext()){return array.keyAt(cursor +1);}else{thrownewNoSuchElementException();}}@Overridepublicint previousIndex(){if(hasPrevious()){if(cursorNowhere){return array.keyAt(cursor);}else{return array.keyAt(cursor -1);}}else{thrownewNoSuchElementException();}}@Overridepublic E next(){if(hasNext()){if(cursorNowhere){
cursorNowhere =false;}
cursor++;return array.valueAt(cursor);}else{thrownewNoSuchElementException();}}@Overridepublic E previous(){if(hasPrevious()){if(cursorNowhere){
cursorNowhere =false;}else{
cursor--;}return array.valueAt(cursor);}else{thrownewNoSuchElementException();}}@Overridepublicvoid add(E object){thrownewUnsupportedOperationException();}@Overridepublicvoid remove(){if(!cursorNowhere){
array.remove(array.keyAt(cursor));
cursorNowhere =true;
cursor--;}else{thrownewIllegalStateException();}}@Overridepublicvoid set(E object){if(!cursorNowhere){
array.setValueAt(cursor, object);}else{thrownewIllegalStateException();}}}
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)
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
privatevoid 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;}}
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>:
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.
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.
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.
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.
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.Respostas:
Parece que encontrei a solução. Eu não tinha notado corretamente a
keyAt(index)
função.Então, eu vou com algo assim:
fonte
Object obj = sparseArray.valueAt(i);
valueAt(i)
é mais rápido queget(key)
, porquevalueAt(i)
ekeyAt(i)
são ambos O (1) , masget(key)
é O (log2 n) , então eu certamente sempre usariavalueAt
.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.fonte
sparseArray.size()
uma variável para que ela não seja chamadasize()
sempre.Ou você acabou de criar seu próprio ListIterator:
fonte
Simples como torta. Apenas certifique-se de buscar o tamanho da matriz antes de executar o loop.
Espero que isto ajude.
fonte
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 -> }
fonte
Para remover todos os elementos do
SparseArray
uso do loop acima, leva aException
.Para evitar isso Siga o código abaixo para remover todos os elementos do
SparseArray
uso de loops normaisfonte
.clear()
que deve ser favorecido.i-=1;
para dar conta do elemento que está faltando. Mas é melhor reverter o loopfor(int i=sparseArray.size()-1; i>=0; i++){...
:; ouwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
Aqui é simples
Iterator<T>
eIterable<T>
implementações paraSparseArray<T>
:Se você deseja iterar não apenas um valor, mas também uma chave:
É útil criar métodos utilitários que retornam
Iterable<T>
eIterable<SparseKeyValue<T>>
:Agora você pode iterar
SparseArray<T>
:fonte
Se você usa o Kotlin, pode usar as funções de extensão como tais, por exemplo:
Você também pode converter para uma lista, se desejar. Exemplo:
Eu acho que pode até ser seguro excluir itens usando
remove
porLongSparseArray
si 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:
Aqui está o uso do LongSparseArray:
E para aqueles que usam Java, você pode usar
LongSparseArrayKt.keyIterator
,LongSparseArrayKt.valueIterator
eLongSparseArrayKt.forEach
, por exemplo. O mesmo para os outros casos.fonte
A resposta é não, porque
SparseArray
não a fornece. Comopst
dito, essa coisa não fornece nenhuma interface.Você pode fazer um loop
0 - size()
e pular os valores que retornamnull
, mas é isso.Como afirmo no meu comentário, se você precisar repetir, use a em
Map
vez de aSparseArray
. Por exemplo, use umTreeMap
que itere em ordem pela chave.fonte
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 ...
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.
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.
Eu acho que idealmente a família SparseArray teria um método getKeys (), mas infelizmente não.
fonte
keyAt
método retorna o valor da enésima chave (no seu exemplokeyAt(1)
retornaria250
), para não ser confundido com oget
que retorna o valor do elemento referenciado pela chave.