Как перебрать SparseArray?

311

Есть ли способ перебрать Java SparseArray (для Android)? Раньше я sparsearrayлегко получал значения по индексу. Я не мог найти один.

Ruzanna
источник
30
Ничего себе, поговорим о полностью нелюбимом классе , соответствует интерфейсам коллекции ZERO ...
1
Вы можете использовать, TreeMap<Integer, MyType>что позволит вам итерировать в порядке по ключу. Как уже говорилось, SparseArray разработан, чтобы быть более эффективным, чем HashMap, но он не допускает итерации.
Джон Б
2
очень, очень маловероятно, что производительность выбранной вами карты станет узким местом в вашем приложении.
Джеффри Блаттман
3
@JeffreyBlattman не означает, что мы должны избегать использования правильной структуры, когда она явно уместна.
морозный
1
@frostymarvelous говорят, что это в два раза быстрее, что, вероятно, означает экономию менее 10 мс. Имеет ли значение 10 мс в схеме грандера приложения? Стоит ли использовать неоптимальный интерфейс, который сложнее понять и поддерживать? Я не знаю ответа на эти вещи, но ответ не "абсолютно использовать разреженный массив независимо".
Джеффри Блатман

Ответы:

537

Кажется, я нашел решение. Я не правильно заметил 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);
}
Ruzanna
источник
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 п) , так что я бы , конечно , всегда использовать valueAt.
Меки
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 () в переменную избыточно. Легко проверить, если вы просто посмотрите на код метода 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();
    }
}
}
0101100101
источник
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 -> }

0101100101
источник
да, вы на самом деле правы. мой плохой, я посмотрел на метки и подумал, что Котлина не должно быть здесь. Но теперь у меня возникли мысли, что этот ответ является хорошей ссылкой на самого Котлина. Хотя вместо использования Anko я бы рекомендовал использовать 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;
    }
}
Sackurise
источник
2
I = -1; в конце ничего не делает. Также есть метод, .clear()который следует назвать предпочтительным.
Пол Войташек
Почему вы используете цикл for () вместо while ()? То, что ты делаешь, не имеет смысла зацикливаться
Фил
Я предполагаю, что Сакурис хотела написать, 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)) {
  // ...
}
Mixel
источник
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себе (не на итераторе), как это происходит в порядке возрастания.


РЕДАКТИРОВАТЬ: кажется, есть даже более простой способ, используя 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, к примеру. То же самое для других случаев.

разработчик Android
источник
-5

Ответ - нет, потому SparseArrayчто не дает этого. Как pstговорится, эта штука не предоставляет никаких интерфейсов.

Вы можете зациклить 0 - size()и пропустить возвращаемые значения null, но это все.

Как я заявляю в своем комментарии, если вам нужно повторить, используйте Mapвместо SparseArray. Например, используйте a, 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/
......
17
Мой ответ неверный, я не буду удалять его, чтобы другие могли учиться.
Тайлер Пфафф