removeIf uygulama detayı


9

Anlayamadığım küçük bir uygulama detay sorum var ArrayList::removeIf. Öncelikle bazı önkoşullar olmadan olduğu gibi koyabileceğimi sanmıyorum.

Bunun gibi: uygulama , aksine , temelde bir yığın . Bir örnek, işleri daha kolay anlaşılır yapmalıdır. Diyelim ki bu listeye sahibim:removeArrayList::remove

List<Integer> list = new ArrayList<>(); // 2, 4, 6, 5, 5
list.add(2);
list.add(4);
list.add(6);
list.add(5);
list.add(5); 

Ve her şeyi bile kaldırmak istiyorum. Yapabilirim:

Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
    int elem = iter.next();
    if (elem % 2 == 0) {
         iter.remove();
    }
}

Veya :

list.removeIf(x -> x % 2 == 0);

Sonuç aynı olacak, ancak uygulama çok farklı. Yana iteratorbir görünümüdür ArrayList, her zaman çağrı remove, altında yatan ArrayListiç dizisi aslında değişecek, yani "iyi" bir duruma getirilmesi gerekir. Yine, her removeçağrısında System::arrayCopydahili olarak çağrılar yapılacaktır .

Kontrast removeIfdaha akıllıdır. Yinelemeyi dahili olarak yaptığı için, işleri daha optimize edebilir. Bunu yapması ilginç.

İlk olarak elemanların kaldırılması gereken indeksleri hesaplar. Bu, önce her dizinde bir değer (a ) bulunduğu bir minik değer BitSetdizisi hesaplanarak yapılır . Birden çok değer bunu a yapar . Belirli bir ofsette bir değer ayarlamak için önce dizideki dizini bulmanız ve ardından karşılık gelen biti ayarlamanız gerekir. Bu çok karmaşık değil. Diyelim ki 65 ve 3. biti ayarlamak istiyorsunuz. Öncelikle bir ihtiyacımız var (çünkü 64 bitin ötesine geçtik, ancak 128'den fazla değil):long64 bitlong64 bitBitSetlong [] l = new long[2]

|0...(60 more bits here)...000|0...(60 more bits here)...000|

Önce dizini bulursunuz: 65 / 64(aslında yaparlar 65 >> 6) ve daha sonra bu dizine ( 1) gerekli biti koyun:

1L << 65 // this will "jump" the first 64 bits, so this will actually become 00000...10. 

Aynı şey 3. Öyle ki, uzun dizi:

|0...(60 more bits here)...010|0...(60 more bits here)...1000|

Kaynak kodda buna BitSet - deathRow(güzel ad!) Diyorlar .


Bu evenörneği burada alalım , neredelist = 2, 4, 6, 5, 5

  • bunlar dizi yineleme ve bu hesaplamak deathRow(burada Predicate::testolduğu true).

deathRow = 7 (000 ... 111)

anlam indeksleri = [0, 1, 2] kaldırılacak

  • artık bu ölüm sırasına göre temel dizideki öğeleri değiştiriyorlar (bunun nasıl yapıldığına dair ayrıntılara girmiyorlar)

iç dizi: [5, 5, 6, 5, 5] olur. Temelde dizinin önünde kalması gereken öğeleri taşırlar.


Sonunda soruyu getirebilirim.

Zamanın bu noktasında şunları biliyorlar:

 w   ->  number of elements that have to remain in the list (2)
 es  ->  the array itself ([5, 5, 6, 5, 5])
 end ->  equal to size, never changed

Bana göre burada yapılacak tek bir adım var:

void getRidOfElementsFromWToEnd() {
    for(int i=w; i<end; ++i){
       es[i] = null;
    }
    size = w;
}

Bunun yerine, bu olur:

private void shiftTailOverGap(Object[] es, int w, int end) {
    System.arraycopy(es, end, es, w, size - end);
    for (int to = size, i = (size -= end - w); i < to; i++)
        es[i] = null;
}

Buradaki değişkenleri bilerek değiştirdim.

Seslenmenin anlamı nedir:

 System.arraycopy(es, end, es, w, size - end);

Özellikle size - endberi end olduğu size her zaman - bu hiç değişmedi (bu her zaman o kadar zero). Bu temelde burada bir NO-OP. Burada hangi köşe davasını kaçırıyorum?


2
Bu ayrıntıları anlamak için günde 1 / 2'yi boşa harcadım ve bu çok açık, bu yöntem başka yerlerde de kullanılıyor. Ben bir aptalım: |
Eugene

Dürüst olmak gerekirse, beni şaşkın bıraktın. Sorunun System.arraycopy(es, end, es, w, size - end)altında yatan uygulama ayrıntılarının kullanımı hakkında mıydı removeIf? Neredeyse hissettim, aradaki başka bir sorunun cevabını okuyordum. (Yukarıdaki yorumu okuyarak) Sonunda önemsiz bir soruya dönüştüğünü hissediyorum. Öyle mi?
Naman

@Naman tam olarak, bu korkunç hakkındaydı System.arrayCopy. Bununla birlikte, ayrıntılar arasında eğlenceli bir yolculuktu (dahili bit seti aynı fikirle ortaya çıkıyor java.util.BitSet)
Eugene

@Naman isterseniz bir NOOP olmayan bir cevap verebilirsiniz (ipucu: range...) ve kabul edeceğim.
Eugene

1
@ Java 8'de eugene kullanıyor java.util.BitSet. Bana göre, BitSetoperasyonların yeniden uygulanması orijinalinden daha iyi görünmüyor. Tüm kelimeleri atlama fırsatı kaçırıldı.
Holger

Yanıtlar:


6

Aradığınız listenin, removeIfile aynı olduğu özel (ortak) duruma bakıyorsunuz ArrayList. Sadece bu durumda, bunun endher zaman eşit olduğunu varsayabilirsiniz size.

Bir karşı örnek şöyle olacaktır:

ArrayList<Integer> l = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7));
l.subList(2, 5).removeIf(i -> i%2 == 1);

Benzer şekilde, removeAllarayacak shiftTailOverGapbir ile endfarklılık değişken sizebir tatbik edilirken subList.

Aradığınızda benzer bir durum ortaya çıkar clear(). Bu durumda, ArrayListkendisini çağırırken gerçekleştirilen gerçek işlem o kadar önemsizdir ki, shiftTailOverGapyöntemi bile çağırmaz . Gibi bir şey kullanırken yalnızca l.subList(a, b).clear(), en son edeceğiz removeRange(a, b)üzerinde lçağırmak, sırayla olacak zaten, kendin öğrendim olarak hangi shiftTailOverGap(elementData, a, b)bir ile bdaha küçük olabilen size.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.