จะวนซ้ำผ่าน SparseArray อย่างไร


311

มีวิธีย้ำผ่าน Java SparseArray (สำหรับ Android) หรือไม่? ฉันเคยsparsearrayได้รับค่าโดยดัชนี ฉันหาไม่เจอ


30
ว้าว, พูดคุยเกี่ยวกับคลาสที่ไม่มีใครรักอย่างสมบูรณ์ , สอดคล้องกับอินเตอร์เฟสคอลเลกชัน ZERO ...

1
คุณสามารถใช้TreeMap<Integer, MyType>อันไหนจะอนุญาตให้คุณวนซ้ำตามลำดับ ตามที่ระบุไว้ SparseArray ได้รับการออกแบบให้มีประสิทธิภาพมากกว่า HashMap แต่ไม่อนุญาตให้ทำซ้ำ
John B

2
มันไม่น่าเป็นไปได้มากที่ประสิทธิภาพของแผนที่ที่คุณเลือกจะเป็นคอขวดในแอปของคุณ
Jeffrey Blattman

3
@JeffreyBlattman ไม่ได้หมายความว่าเราควรหลีกเลี่ยงการใช้โครงสร้างที่เหมาะสมเมื่อมันเหมาะสมอย่างชัดเจน
frostymarvelous

1
@ frostymarvelous บอกว่ามันเร็วกว่าสองเท่านั่นอาจหมายถึงการประหยัดเวลาน้อยกว่า 10ms 10ms มีความเกี่ยวข้องในแบบแผนของแอปหรือไม่ ควรใช้อินเทอร์เฟซย่อยที่ดีที่สุดซึ่งยากต่อการเข้าใจและบำรุงรักษาหรือไม่? ฉันไม่รู้คำตอบของสิ่งเหล่านั้น แต่คำตอบคือ "ไม่ต้องใช้อาร์เรย์โดยไม่คำนึงถึง"
Jeffrey Blattman

คำตอบ:


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) กำหนดดัชนีในช่วง 0 ... ขนาด () - 1 ส่งคืนคีย์จากการทำแผนที่คีย์ - ค่าดัชนีที่ SparseArray นี้จัดเก็บ" มันใช้งานได้ดีสำหรับฉันแม้ในกรณีที่คุณอธิบายไว้
Ruzanna

12
เป็นการดีกว่าที่จะคำนวณขนาดของอาร์เรย์และใช้ค่าคงที่เป็นวง
Dmitry Zaytsev

25
การใช้ฟังก์ชั่น valueAt โดยตรงนั้นง่ายกว่าไหม
Milan Krstic

34
สิ่งนี้จะใช้ได้ภายในวง:Object obj = sparseArray.valueAt(i);
Florian

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 () มีประโยชน์ (และเร็วกว่าโซลูชันที่ยอมรับ) หากการวนซ้ำของคุณไม่สนใจเกี่ยวกับคีย์เช่น: การนับลูปเกิดขึ้นกับค่าเฉพาะ
Sogger

2
รับsparseArray.size()ในตัวแปรเดียวดังนั้นมันจะไม่เรียกsize()ทุกครั้ง
Pratik Butani

4
มันซ้ำซ้อนในการคัดลอกขนาด () ไปยังตัวแปร ง่ายต่อการตรวจสอบว่าคุณเพิ่งดูรหัสขนาด () วิธี ฉันไม่เข้าใจว่าทำไมคุณถึงไม่ก่อนที่คุณจะแนะนำสิ่งต่าง ๆ ... ฉันจำได้เมื่อ 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
IMHO ดูเหมือนว่าจะเป็นเรื่องของวิศวกรรมมากไป มันน่าประทับใจมาก
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 -> }


ใช่คุณพูดถูกจริงๆ ไม่ดีของฉันฉันดูที่แท็กและคิดว่า Kotlin ไม่ควรอยู่ที่นี่ แต่ตอนนี้มีความคิดที่สองว่าคำตอบนี้เป็นการอ้างอิงที่ดีกับ Kotlin แม้ว่าแทนที่จะใช้ 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()ควรได้รับการสนับสนุน
Paul Woitaschek

ทำไมคุณถึงใช้ห่วง for () แทนที่จะใช้เวลาสักครู่ () สิ่งที่คุณทำไม่สมเหตุสมผลสำหรับการวนซ้ำ
ฟิล A

ฉันถือว่า Sackurise ต้องการเขียนi-=1;ถึงบัญชีสำหรับองค์ประกอบที่หายไปในขณะนี้ แต่จะเป็นการดีกว่าที่จะยกเลิกการวนซ้ำ: for(int i=sparseArray.size()-1; i>=0; i++){...; หรือwhile (sparseArray.size()>0) { int key=sparseArray.keyAt(0);...
ths

การอ้างอิงเช่น "การวนรอบด้านบน" ไม่สมเหตุสมผลเลย
เหลือเชื่อ Jan

ฉันคิดว่าจุดของ 'ตัววนซ้ำ' คือการลบวัตถุอย่างปลอดภัย ฉันไม่เห็นตัวอย่างของคลาส 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ตัวอย่างเช่นใช้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วิธีคืนค่าของคีย์ nth (ในตัวอย่างของคุณkeyAt(1)จะส่งคืน250) เพื่อไม่ให้สับสนgetซึ่งจะส่งคืนค่าขององค์ประกอบที่อ้างอิงโดยคีย์
Eborbob

ฉันไม่แน่ใจว่าสิ่งนี้คืออะไรในความคิดเห็นของคุณ คุณยอมรับว่าคำตอบของคุณผิดหรือคุณกำลังพูดว่าความคิดเห็นของฉันผิดหรือเปล่า? หากหลังโปรดตรวจสอบdeveloper.android.com/reference/android/util/…
Eborbob

17
คำตอบของฉันผิดฉันจะไม่ลบมันเพื่อให้คนอื่นสามารถเรียนรู้ได้
Tyler Pfaff
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.