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


311

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


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

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

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


да, вы на самом деле правы. мой плохой, я посмотрел на метки и подумал, что Котлина не должно быть здесь. Но теперь у меня возникли мысли, что этот ответ является хорошей ссылкой на самого Котлина. Хотя вместо использования 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;
    }
}

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)) {
  // ...
}

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, к примеру. То же самое для других случаев.


-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
Мой ответ неверный, я не буду удалять его, чтобы другие могли учиться.
— Тайлер Пфафф
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.