remove หากรายละเอียดการใช้งาน


9

ArrayList::removeIfฉันมีคำถามรายละเอียดการดำเนินงานขนาดเล็กที่ฉันไม่เข้าใจใน ฉันไม่คิดว่าฉันจะทำได้อย่างที่ไม่เคยมีมาก่อน

เป็นเช่น: การดำเนินการนั้นเป็นจำนวนมาก ซึ่งแตกต่างจากremove ArrayList::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); 

และฉันต้องการลบองค์ประกอบทุกอย่างที่เท่ากัน ฉันทำได้:

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

หรือ :

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

ผลลัพธ์จะเหมือนกัน แต่การนำไปใช้นั้นแตกต่างกันมาก เนื่องจากiteratorเป็นมุมมองของArrayListทุกครั้งที่ฉันเรียกremoveมันArrayListจะต้องถูกนำไปสู่สถานะ "ดี" ซึ่งหมายความว่าอาร์เรย์ภายในจะเปลี่ยนไปจริง ๆ ทุกครั้งที่มีการโทรremoveจะมีการโทรSystem::arrayCopyภายใน

ในทางตรงกันข้ามremoveIfอย่างชาญฉลาด เนื่องจากเป็นการวนซ้ำภายในจึงสามารถทำให้สิ่งต่าง ๆ มีประสิทธิภาพสูงสุด วิธีนี้เป็นสิ่งที่น่าสนใจ

ก่อนอื่นให้คำนวณดัชนีที่ควรลบองค์ประกอบออก สิ่งนี้ทำได้โดยการคำนวณขนาดเล็กBitSetซึ่งเป็นอาร์เรย์ของlongค่าที่แต่ละดัชนีอยู่ใน64 bitค่า (a long) หลายค่าทำนี้64 bit BitSetในการตั้งค่าที่ออฟเซ็ตเฉพาะอันดับแรกคุณต้องค้นหาดัชนีในอาร์เรย์จากนั้นตั้งค่าบิตที่สอดคล้องกัน มันไม่ซับซ้อนมาก สมมติว่าคุณต้องการตั้งค่าบิต 65 และ 3 อันดับแรกเราต้องการlong [] l = new long[2](เพราะเรามีมากกว่า 64 บิต แต่ไม่เกิน 128):

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

ก่อนอื่นคุณจะพบดัชนี: 65 / 64(พวกมันทำจริง65 >> 6) จากนั้นในดัชนีนั้น ( 1) ใส่บิตที่ต้องการ:

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

3สิ่งเดียวกันสำหรับ เช่นนั้นอาร์เรย์แบบยาวจะกลายเป็น:

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

ในซอร์สโค้ดพวกเขาเรียก BitSet นี้ - deathRow(ชื่อดี!)


ลองดูevenตัวอย่างที่นี่ที่ไหนlist = 2, 4, 6, 5, 5

  • พวกเขาย้ำอาร์เรย์และคำนวณนี้deathRow(ที่Predicate::testเป็นtrue)

deathRow = 7 (000 ... 111)

ความหมาย indexes = [0, 1, 2] จะถูกลบออก

  • ตอนนี้พวกเขาแทนที่องค์ประกอบในอาเรย์พื้นฐานตาม DeathRow นั้น (ไม่ได้เข้าไปในรายละเอียดวิธีการทำ)

อาร์เรย์ภายในจะกลายเป็น: [5, 5, 6, 5, 5] โดยทั่วไปแล้วพวกเขาย้ายองค์ประกอบที่ควรจะอยู่ด้านหน้าของอาร์เรย์


ในที่สุดฉันก็สามารถนำคำถาม

เมื่อถึงจุดนี้พวกเขารู้ว่า:

 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

สำหรับฉันมีขั้นตอนเดียวที่ต้องทำที่นี่:

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

สิ่งนี้เกิดขึ้นแทน:

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

ฉันได้เปลี่ยนชื่อตัวแปรตามวัตถุประสงค์ที่นี่

จุดในการโทรคืออะไร:

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

โดยเฉพาะอย่างยิ่งsize - endเนื่องจากend เป็น sizeตลอดเวลา - มันไม่เคยเปลี่ยนแปลง (ดังนั้นนี่คือเสมอzero) นี่เป็นแบบไม่โอพีที่นี่ ฉันหายไปตรงไหนที่นี่?


2
ฉันเพิ่งเสียเวลา 1/2 วันทำความเข้าใจรายละเอียดเหล่านี้และนี่ชัดเจนดังนั้นวิธีนี้ใช้ในที่อื่นด้วย ฉันเป็นคนงี่เง่า: |
ยูจีน

จริงๆแล้วคุณทำให้ฉันสับสน เป็นคำถามของรอบการใช้งานของSystem.arraycopy(es, end, es, w, size - end)เป็นรายละเอียดการดำเนินงานพื้นฐานของremoveIf? ฉันเกือบจะรู้สึกว่าฉันกำลังอ่านคำตอบของคำถามอื่นในระหว่างนั้น (อ่านความคิดเห็นด้านบน) ฉันรู้สึกว่ามันจบลงด้วยคำถามเล็ก ๆ น้อย ๆ ในที่สุด เป็นอย่างนั้นเหรอ?
Naman

@Naman แน่นอนว่ามันเกี่ยวกับความหวาดกลัวSystem.arrayCopyนั้น อย่างไรก็ตามมันเป็นการเดินทางที่สนุกผ่านรายละเอียด (ชุดบิตภายในนั้นกลับกลายเป็นความคิดเดียวกันjava.util.BitSet)
Eugene

@Naman ถ้าคุณต้องการคุณสามารถให้คำตอบที่ไม่ใช่ NOOP (คำใบ้: range... ) และฉันจะยอมรับมัน
ยูจีน

1
@Eugene ใน Java 8 มันใช้งานjava.util.BitSetได้ สำหรับฉันแล้วการนำการดำเนินการกลับมาใช้ใหม่BitSetไม่ได้ดูดีกว่าของเดิมอย่างชัดเจน พลาดโอกาสในการข้ามคำทั้งหมดไป
Holger

คำตอบ:


6

คุณกำลังมองหาที่เฉพาะเจาะจง (ธรรมดา) กรณีที่รายการคุณเรียกเมื่อเป็นเช่นเดียวกับremoveIf ArrayListเฉพาะในกรณีนี้คุณสามารถสรุปได้ว่าอยู่เสมอเท่ากับendsize

ตัวอย่างเคาน์เตอร์จะเป็น:

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

ในทำนองเดียวกันremoveAllจะเรียกshiftTailOverGapกับendการโต้แย้งซึ่งอาจแตกต่างจากเมื่อถูกนำไปใช้กับsizesubList

clear()สถานการณ์ที่คล้ายกันเกิดขึ้นเมื่อคุณเรียก ในกรณีดังกล่าวการดำเนินการจริงที่ดำเนินการเมื่อเรียกใช้ArrayListด้วยตัวเองมีความสำคัญมากจนไม่สามารถเรียกใช้shiftTailOverGapเมธอดได้ เฉพาะเมื่อใช้สิ่งที่ต้องการl.subList(a, b).clear()ก็จะจบลงที่removeRange(a, b)บนlซึ่งจะเปิดในขณะที่คุณพบแล้วออกตัวเองวิงวอนshiftTailOverGap(elementData, a, b)ด้วยซึ่งอาจจะมีขนาดเล็กกว่าbsize

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