Bagaimana cara mengulang melalui SparseArray?

311

Apakah ada cara untuk beralih ke Java SparseArray (untuk Android)? Saya terbiasa sparsearraydengan mudah mendapatkan nilai berdasarkan indeks. Saya tidak dapat menemukannya.

Ruzanna
sumber
30
Wow, bicarakan kelas yang benar - benar tidak dicintai , sesuai dengan antarmuka koleksi ZERO ...
1
Anda bisa menggunakan TreeMap<Integer, MyType>yang akan memungkinkan Anda untuk mengulang secara berurutan. Seperti yang dinyatakan, SparseArray dirancang agar lebih efisien daripada HashMap, tetapi tidak memungkinkan iterasi.
John B
2
itu sangat, sangat tidak mungkin bahwa kinerja imp peta yang Anda pilih akan menjadi hambatan dalam aplikasi Anda.
Jeffrey Blattman
3
@JeffreyBlattman tidak berarti kita harus menghindari menggunakan struktur yang tepat ketika itu jelas tepat.
frostymarvelous
1
@ Frostymarvelous mengatakan ini DUA KALI lebih cepat, itu mungkin berarti penghematan kurang dari 10 ms. Apakah 10 ms relevan dalam skema aplikasi yang lebih besar? Apakah layak menggunakan antarmuka sub-optimal yang sulit dipahami dan dipelihara? Saya tidak tahu jawaban untuk hal-hal itu, tetapi jawabannya bukan "benar-benar menggunakan array jarang".
Jeffrey Blattman

Jawaban:

537

Sepertinya saya menemukan solusinya. Saya belum benar-benar memperhatikan keyAt(index)fungsinya.

Jadi saya akan pergi dengan sesuatu seperti ini:

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
sumber
25
dokumentasi menyatakan bahwa "keyAt (int index) Diberikan indeks dalam kisaran 0 ... size () - 1, mengembalikan kunci dari pemetaan nilai kunci indeks yang disimpan SparseArray ini." jadi itu berfungsi dengan baik untuk saya bahkan untuk kasus yang dijelaskan oleh Anda.
Ruzanna
12
lebih baik untuk menghitung ulang ukuran array dan menggunakan nilai konstan dalam loop.
Dmitry Zaytsev
25
Bukankah lebih mudah menggunakan fungsi valueAt secara langsung di sini?
Milan Krstic
34
Ini akan bekerja juga di dalam loop:Object obj = sparseArray.valueAt(i);
Florian
27
valueAt(i)lebih cepat dari get(key), karena valueAt(i)dan keyAt(i)keduanya O (1) , tetapi get(key)adalah O (log2 n) , jadi saya pasti selalu menggunakan valueAt.
Mecki
180

Jika Anda tidak peduli dengan kunci, maka valueAt(int)dapat digunakan untuk sementara iterasi melalui array jarang untuk mengakses nilai secara langsung.

for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
    Object obj = sparseArray.valueAt(i);
}
Pogo Lin
sumber
7
Menggunakan valueAt () berguna (dan lebih cepat dari solusi yang diterima) jika iterasi Anda tidak peduli dengan kunci, yaitu: kejadian penghitungan loop dari nilai tertentu.
Sogger
2
Ambil sparseArray.size()satu variabel sehingga tidak akan menelepon size()setiap waktu.
Pratik Butani
4
Itu berlebihan untuk menyalin ukuran () ke variabel. Mudah untuk memeriksa apakah Anda hanya melihat kode metode size (). Saya tidak dapat mengerti mengapa Anda tidak sebelum menyarankan hal-hal seperti itu ... Saya ingat suatu waktu 20 tahun yang lalu di mana kami memiliki daftar tertaut sederhana yang benar-benar harus menghitung ukurannya setiap kali Anda memintanya, tetapi saya tidak percaya bahwa hal-hal seperti itu masih ada ...
Jan
Apakah ini dijamin dalam urutan utama?
HughHughTeotl
18

Atau Anda baru saja membuat ListIterator Anda sendiri:

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
sumber
9
IMHO sepertinya sedikit over-engineering. Ini luar biasa
hrules6872
12

Sederhana seperti Pie. Pastikan Anda mengambil ukuran array sebelum benar-benar melakukan loop.

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

Semoga ini membantu.

Pascal
sumber
11

Bagi siapa pun yang menggunakan Kotlin, jujur ​​cara termudah untuk beralih di SparseArray adalah: Gunakan ekstensi Kotlin dari Anko atau Android KTX ! (kredit untuk Yazazzello karena menunjukkan Android KTX)

Cukup telepon forEach { i, item -> }

0101100101
sumber
ya, Anda sebenarnya benar. salahku, aku melihat pada tag dan berpikir bahwa Kotlin seharusnya tidak berada di sini. Tetapi sekarang memiliki pemikiran kedua bahwa jawaban ini adalah referensi yang baik untuk Kotlin itu sendiri. Meskipun alih-alih menggunakan Anko, saya sarankan untuk menggunakan android.github.io/android-ktx/core-ktx (jika Anda bisa mengedit jawaban dengan baik dan menambahkan android-ktx saya akan
membatalkannya
@Yazazzello, hei, saya bahkan tidak tahu tentang Android KTX, poin bagus!
0101100101
7

Untuk menghapus semua elemen dari SparseArraymenggunakan mengarah perulangan di atas Exception.

Untuk menghindari ini Ikuti kode di bawah ini untuk menghapus semua elemen dari SparseArraymenggunakan loop normal

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
sumber
2
I = -1; pada akhirnya tidak melakukan apa-apa. Juga ada metode yang disebut .clear()yang harus disukai.
Paul Woitaschek
Mengapa Anda menggunakan loop for () alih-alih sementara ()? Apa yang Anda lakukan tidak masuk akal untuk diulang
Phil A
Saya berasumsi Sackurise ingin menulis i-=1;ke akun untuk elemen yang sekarang hilang. Tapi itu lebih baik untuk kembali loop: for(int i=sparseArray.size()-1; i>=0; i++){...; atauwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
THS
Referensi seperti "perulangan di atas" tidak masuk akal sama sekali.
Luar biasa
Saya pikir titik 'iterator' adalah penghapusan objek yang aman. Saya belum melihat contoh kelas Iterator dengan sparseArrays seperti ada untuk hashmaps. Ini yang paling dekat dengan mengatasi penghapusan objek aman, saya harap ini berfungsi tanpa pengecualian modifikasi bersamaan.
Androidcoder
5

Berikut ini sederhana Iterator<T>dan Iterable<T>implementasi untuk 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);
    }
}

Jika Anda ingin mengulangi tidak hanya nilai tetapi juga kunci:

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

Berguna untuk membuat metode utilitas yang mengembalikan Iterable<T>dan 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);
    }
}

Sekarang Anda dapat mengulangi SparseArray<T>:

SparseArray<String> a = ...;

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

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

Jika Anda menggunakan Kotlin, Anda dapat menggunakan fungsi ekstensi seperti itu, misalnya:

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

Anda juga dapat mengonversi ke daftar, jika diinginkan. Contoh:

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

Saya pikir itu bahkan mungkin aman untuk item menghapus menggunakan removepada LongSparseArrayitu sendiri (bukan pada iterator), seperti di urutan menaik.


EDIT: Sepertinya ada cara yang lebih mudah, dengan menggunakan collection-ktx (contoh di sini ). Ini diterapkan dengan cara yang sangat mirip dengan apa yang saya tulis, secara aktif.

Gradle membutuhkan ini:

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

Inilah penggunaan untuk LongSparseArray:

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

Dan bagi mereka yang menggunakan Java, Anda dapat menggunakan LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratordan LongSparseArrayKt.forEach, misalnya. Sama untuk kasus lainnya.

pengembang android
sumber
-5

Jawabannya adalah tidak karena SparseArraytidak menyediakannya. Seperti yang pstdikatakan, benda ini tidak menyediakan antarmuka apa pun.

Anda bisa mengulang dari 0 - size()dan melewatkan nilai yang kembali null, tetapi hanya itu saja.

Seperti yang saya nyatakan dalam komentar saya, jika Anda perlu menggunakan it Mapbukan a SparseArray. Misalnya, gunakan TreeMaptombol yang diurutkan berdasarkan kunci.

TreeMap<Integer, MyType>
John B
sumber
-6

Jawaban yang diterima memiliki beberapa lubang di dalamnya. Keindahan SparseArray adalah memungkinkan celah di indeces. Jadi, kita bisa memiliki dua peta seperti itu, dalam SparseArray ...

(0,true)
(250,true)

Perhatikan bahwa ukuran di sini adalah 2. Jika kita mengulangi ukuran lebih, kita hanya akan mendapatkan nilai untuk nilai yang dipetakan ke indeks 0 dan indeks 1. Jadi pemetaan dengan kunci 250 tidak diakses.

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

Cara terbaik untuk melakukan ini adalah untuk mengulangi ukuran set data Anda, kemudian periksa indeces tersebut dengan get () pada array. Berikut ini adalah contoh dengan adaptor di mana saya mengizinkan penghapusan beberapa item.

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

Saya pikir idealnya keluarga SparseArray akan memiliki metode getKeys (), tetapi sayangnya tidak.

Tyler Pfaff
sumber
4
Anda salah - keyAtmetode mengembalikan nilai kunci ke-n (dalam contoh Anda keyAt(1)akan kembali 250), jangan bingung dengan getyang mengembalikan nilai elemen yang dirujuk oleh kunci.
Eborbob
Saya tidak yakin apa 'ini' dalam komentar Anda. Apakah Anda mengakui bahwa jawaban Anda salah, atau apakah Anda mengatakan bahwa komentar saya salah? Jika yang terakhir silakan periksa developer.android.com/reference/android/util/…
Eborbob
17
Jawaban saya salah, saya tidak akan menghapusnya sehingga orang lain bisa belajar.
Tyler Pfaff