Як перебрати через SparseArray?


311

Чи є спосіб перебрати Java SparseArray (для Android)? Я звик sparsearrayлегко отримувати значення за індексом. Я не зміг його знайти.


30
Нічого, поговоримо про абсолютно нелюбимий клас , відповідає інтерфейсам колекції ZERO ...

1
Ви можете скористатися символом, TreeMap<Integer, MyType>який дозволить вам повторити порядок за ключем. Як зазначалося, SparseArray розроблений для того, щоб бути більш ефективним, ніж HashMap, але він не дозволяє ітерацію.
Джон Б

2
це дуже, дуже малоймовірно, що ефективність вибраного вами карти буде вузьким місцем у вашому додатку.
Джефрі Блатман

3
@JeffreyBlattman не означає, що нам слід уникати використання правильної структури, коли це явно доречно.
frostymarvelous

1
@frostymarvelous говорять, що ДВІСЬ так швидко, що, ймовірно, означає економію менше 10 мс. Чи доречні 10 мс у схемі грандіознішої програми? Чи варто використовувати неоптимальний інтерфейс, який важче зрозуміти та підтримувати? Я не знаю відповіді на ці речі, але відповідь не "абсолютно використовувати розріджений масив незалежно".
Джефрі Блатман

Відповіді:


536

Здається, я знайшов рішення. Я неправильно помітив цю keyAt(index)функцію.

Тож я піду з чимось таким:

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
в документації зазначено, що "keyAt (int index) З огляду на індекс у діапазоні 0 ... size () - 1, повертає ключ із indexth-зіставленням ключа-значення, яке зберігає цей SparseArray." тож для мене це добре працює навіть у випадку, описаному вами.
Рузанна

12
краще заздалегідь підрахувати розмір масиву і використовувати постійне значення в циклі.
Дмитро Зайцев

25
Чи не було б простіше використовувати тут функцію valueAt?
Мілан Крістік

34
Це також працюватиме всередині циклу:Object obj = sparseArray.valueAt(i);
Флоріан

27
valueAt(i)швидше get(key), тому що valueAt(i)і keyAt(i)обидва є O (1) , але get(key)є O (log2 n) , тому я, безумовно, завжди користувався valueAt.
Mecki

180

Якщо вам не важливо клавіш, ви valueAt(int)можете використовувати їх для перегляду через розріджений масив для прямого доступу до значень.

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

7
Використання valueAt () корисно (і швидше, ніж прийняте рішення), якщо ваша ітерація не стосується ключів, тобто: циклічного підрахунку подій певного значення.
Соггер

2
Візьміть sparseArray.size()одну змінну, щоб вона не дзвонила size()кожен раз.
Пратік Бутані

4
Копіювати розмір () на змінну - це надлишково. Легко перевірити, якщо ви просто подивитесь на код методу size (). Я не можу зрозуміти, чому ви раніше не пропонували подібних речей ... Я пам’ятаю час 20 років тому, коли ми мали прості перелічені списки, які справді мали рахувати їх розмір кожного разу, коли ви їх просили, але я не вірю що такі речі все ще існують ...
Неймовірний

Чи гарантовано це в порядку?
HughHughTeotl

18

Отже, ви просто створили власний 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
ІМХО, здається, трохи надмірна інженерія. Це приголомшливе Тхо
hrules6872

12

Простий, як пиріг. Просто переконайтеся, що ви отримали розмір масиву, перш ніж виконувати цикл.

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

Сподіваюсь, це допомагає.


11

Для тих, хто використовує Kotlin, чесно кажучи, найпростішим способом ітерації через SparseArray є: Використовуйте розширення Kotlin від Anko або Android KTX ! (заслуга Yazazzello за вказівку на Android KTX)

Просто зателефонуйте forEach { i, item -> }


так, ти насправді маєш рацію. мені погано, я подивився на теги і подумав, що Котліна тут не повинно бути. Але тепер подумавши, що ця відповідь є хорошим посиланням на самого Котліна. Хоча замість Анко я б рекомендував використовувати android.github.io/android-ktx/core-ktx (якщо ви зможете відредагувати свою відповідь і додати android-ktx, я підтверджую її)
Yazazzello

@Yazazzello Ей, я навіть не знав про Android KTX, хороший момент!
0101100101

7

Для видалення всіх елементів із SparseArrayвикористання вищевказаних циклів призводить до Exception.

Щоб цього уникнути Дотримуйтесь наведеного нижче коду, щоб видалити всі елементи із SparseArrayзвичайних циклів

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
I = -1; в кінці нічого не робить. Також існує метод, .clear()який називається, якому слід віддавати перевагу.
Пол Войташек

Чому б ви використовували цикл for () замість часу ()? Те, що ви робите, не має сенсу для циклу
Філ А

я припускаю, що Саккуріз хотів написати i-=1;для обліку зниклого елемента. Але це краще повернутися в цикл: for(int i=sparseArray.size()-1; i>=0; i++){...; абоwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
тис

Посилання на зразок "вищеописаного циклу" взагалі не мають сенсу.
Неймовірний січень

Я подумав, що пунктом ітератора є безпечне видалення об'єкта. Я не бачив жодних прикладів класу Iterator з sparseArrays, як для хешмапів. Це найближче до вирішення питання щодо безпечного видалення об'єкта, я сподіваюся, що він працює без одночасних винятків модифікації.
Androidcoder

5

Ось прості Iterator<T>та Iterable<T>реалізації для 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);
    }
}

Якщо ви хочете повторити не тільки значення, але і ключ:

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

Корисно створити корисні методи, які повертаються Iterable<T>та 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);
    }
}

Тепер ви можете повторити SparseArray<T>:

SparseArray<String> a = ...;

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

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

4

Якщо ви використовуєте Kotlin, ви можете використовувати функції розширення як такі, наприклад:

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

Ви також можете конвертувати в список, якщо бажаєте. Приклад:

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

Я думаю, що можливо навіть безпечно видаляти елементи, використовуючи removeна LongSparseArrayсобі (а не на ітераторі), як це відбувається у порядку зростання.


EDIT: Здається, існує навіть простіший спосіб, використовуючи collection-ktx (приклад тут ). Він реалізований дуже схожим чином на те, що я написав, актально.

Gradle вимагає цього:

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

Ось використання для LongSparseArray:

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

А для тих, хто використовує Java, ви можете використовувати LongSparseArrayKt.keyIterator, LongSparseArrayKt.valueIteratorі LongSparseArrayKt.forEach, наприклад. Те ж саме для інших випадків.


-5

Відповідь "ні", тому SparseArrayщо її не передбачено. Як pstсказано, ця річ не забезпечує ніяких інтерфейсів.

Ви можете обмінятись 0 - size()та пропустити значення, що повертаютьсяnull , але це стосується цього.

Як я зазначаю у своєму коментарі, якщо вам потрібно повторити, використовуйте Mapзамість SparseArray. Наприклад, використовуйте TreeMapітерацію, яка відбувається за порядком за ключем.

TreeMap<Integer, MyType>

-6

У прийнятій відповіді є деякі отвори. Краса SparseArray полягає в тому, що він дозволяє прогалини в індексах. Отже, у нас може бути дві карти на зразок SparseArray ...

(0,true)
(250,true)

Зауважте, розмір тут буде 2. Якщо ми повторимо розмір, ми отримаємо лише значення для значень, відображених на індекс 0 та індекс 1. Отже, до відображення з ключем 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);
}

Найкращий спосіб зробити це - повторити розмір набору даних, а потім перевірити ці індекси за допомогою get () на масиві. Ось приклад із адаптером, де я дозволяю пакетне видалення елементів.

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

Я думаю, що в ідеалі сім'я SparseArray мала би метод getKeys (), але, на жаль, це не так.


4
Ви помиляєтесь - keyAtметод повертає значення n-го ключа (у вашому прикладі keyAt(1)повернеться 250), не плутати з getяким повертає значення елемента, на який посилається ключ.
Eborbob

Я не впевнений, що "це" у вашому коментарі. Ви визнаєте, що ваша відповідь неправильна, чи ви говорите, що мій коментар невірний? Якщо останні, будь ласка, перевірте developer.android.com/reference/android/util/…
Eborbob

17
Моя відповідь неправильна, я не буду її видаляти, щоб інші могли вчитися.
Тайлер Пфафф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.