Java 8 lambda รับและลบองค์ประกอบจากรายการ


91

ด้วยรายการองค์ประกอบฉันต้องการรับองค์ประกอบที่มีคุณสมบัติที่กำหนดและลบออกจากรายการ ทางออกที่ดีที่สุดที่ฉันพบคือ:

ProducerDTO p = producersProcedureActive
                .stream()
                .filter(producer -> producer.getPod().equals(pod))
                .findFirst()
                .get();
producersProcedureActive.remove(p);

เป็นไปได้หรือไม่ที่จะรวมการรับและลบในนิพจน์แลมบ์ดา


9
ดูเหมือนว่าจะเป็นกรณีคลาสสิกเมื่อใช้ลูปและตัววนซ้ำแทน
chrylis -cautiouslyoptimistic-

1
@chrylis ฉันไม่เห็นด้วย;) เราคุ้นเคยกับการเขียนโปรแกรมที่จำเป็นมากจนวิธีอื่น ๆ ฟังดูแปลกใหม่เกินไป ลองนึกภาพว่าความเป็นจริงเป็นอีกทางหนึ่งหรือไม่: เราคุ้นเคยกับการเขียนโปรแกรมเชิงฟังก์ชันเป็นอย่างมากและมีการเพิ่มกระบวนทัศน์ใหม่ที่จำเป็นใน Java คุณจะบอกว่านี่เป็นกรณีคลาสสิกสำหรับสตรีมเพรดิเคตและตัวเลือกหรือไม่?
fps

9
อย่าโทรมาget()ที่นี่! คุณไม่รู้ว่ามันว่างเปล่าหรือไม่ คุณจะเกิดข้อยกเว้นหากองค์ประกอบไม่อยู่ที่นั่น ให้ใช้หนึ่งในวิธีการที่ปลอดภัยเช่น ifPresent, orElse, orElseGet หรือ orElseThrow แทน
Brian Goetz

@federicoPeraltaSchaffner อาจเป็นเรื่องของรสชาติ แต่ฉันก็ขอเสนอไม่ให้รวมทั้งสองอย่างเข้าด้วยกัน เมื่อฉันเห็นโค้ดบางตัวได้รับค่าโดยใช้สตรีมโดยปกติฉันจะถือว่าการดำเนินการในสตรีมไม่มีผลข้างเคียง การผสมผสานในการลบองค์ประกอบอาจส่งผลให้โค้ดที่ผู้อ่านพลาดไปได้
Matthias Wimmer

เพียงเพื่อชี้แจง: คุณต้องการลบองค์ประกอบทั้งหมดlistที่Predicateเป็นจริงหรือเพียงองค์ประกอบแรก (อาจเป็นศูนย์หนึ่งหรือหลายองค์ประกอบ)?
Kedar Mhaswade

คำตอบ:


154

เพื่อลบองค์ประกอบออกจากรายการ

objectA.removeIf(x -> conditions);

เช่น:

objectA.removeIf(x -> blockedWorkerIds.contains(x));

List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");

List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");

str1.removeIf(x -> str2.contains(x)); 

str1.forEach(System.out::println);

เอาท์พุท: A B C


ฉันขอแนะนำสิ่งนี้เป็นคำตอบที่ดีที่สุด
Sergey Pauk

1
นี่คือเรียบร้อยมาก คุณอาจต้องใช้ equals / hashCode หากไม่ได้ทำกับอ็อบเจ็กต์ของคุณเอง (ในตัวอย่างนี้ใช้ String ซึ่งมีโดยค่าเริ่มต้น)
bram000

17
คำตอบของ IMHO ไม่ได้พิจารณาถึงส่วน "get": removeIfเป็นโซลูชันที่ยอดเยี่ยมในการลบองค์ประกอบออกจากคอลเล็กชัน แต่จะไม่ส่งคืนองค์ประกอบที่ถูกลบ
Marco Stramezzi

มันเป็นโหมดที่ง่ายมากในการแยกวัตถุออกจาก java ArrayList ขอบคุณมาก ทำงานได้ดีสำหรับฉัน
Marcelo Rebouças

3
สิ่งนี้ไม่ตอบคำถาม ข้อกำหนดคือการลบองค์ประกอบออกจากรายการและลบรายการ / รายการไปยังรายการใหม่
Shanika Ediriweera

35

แม้ว่าด้ายค่อนข้างเก่ายังคงคิดว่าเพื่อให้แก้ปัญหา - Java8ใช้

ทำให้การใช้งานremoveIfฟังก์ชั่น ความซับซ้อนของเวลาคือO(n)

producersProcedureActive.removeIf(producer -> producer.getPod().equals(pod));

การอ้างอิง API: removeIf docs

สมมติฐาน: producersProcedureActiveเป็นList

หมายเหตุ: ด้วยวิธีนี้คุณจะไม่สามารถระงับรายการที่ถูกลบได้


นอกจากนี้ในการลบองค์ประกอบออกจากรายการ OP ยังต้องการการอ้างอิงถึงองค์ประกอบ
eee

@eee: ขอบคุณมากที่ชี้ให้เห็น ฉันพลาดส่วนนั้นจากคำถามเดิมของ OP
asifsid88

โปรดทราบว่าสิ่งนี้จะลบรายการทั้งหมดที่ตรงกับเงื่อนไข แต่ดูเหมือนว่า OP จะต้องลบเฉพาะรายการแรกเท่านั้น (OP ใช้ findFirst ())
nantitv

18

พิจารณาใช้ vanilla java iterators เพื่อดำเนินการ:

public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
    T value = null;
    for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
        if (test.test(value = it.next())) {
            it.remove();
            return value;
        }
    return null;
}

ข้อดี :

  1. เป็นธรรมดาและชัดเจน
  2. มันข้ามผ่านเพียงครั้งเดียวและถึงองค์ประกอบที่ตรงกันเท่านั้น
  3. คุณสามารถทำมันใด ๆIterableได้โดยไม่ต้องstream()สนับสนุน(อย่างน้อยผู้ที่ดำเนินการremove()ใน iterator ของพวกเขา)

ข้อเสีย :

  1. คุณไม่สามารถดำเนินการเป็นนิพจน์เดียวได้ (ต้องใช้วิธีการเสริมหรือตัวแปร)

ในส่วนของ

เป็นไปได้หรือไม่ที่จะรวมการรับและลบในนิพจน์แลมบ์ดา

คำตอบอื่น ๆ แสดงให้เห็นอย่างชัดเจนว่าเป็นไปได้ แต่คุณควรตระหนัก

  1. การค้นหาและการลบอาจผ่านรายการสองครั้ง
  2. ConcurrentModificationException อาจถูกโยนทิ้งเมื่อนำองค์ประกอบออกจากรายการที่กำลังทำซ้ำ

5
ฉันชอบโซลูชันนี้ แต่โปรดทราบว่ามีข้อเสียร้ายแรงอย่างหนึ่งที่คุณพลาด: การนำไปใช้งานซ้ำได้หลายremove()วิธีมีวิธีการที่ทำให้ UOE (ไม่ใช่คอลเลกชันของ JDK แน่นอน แต่ฉันคิดว่ามันไม่ยุติธรรมที่จะพูดว่า "ใช้ได้กับ Iterable ใด ๆ ")
Brian Goetz

ฉันคิดว่าเราสามารถสันนิษฐานได้ว่าถ้าองค์ประกอบทั่วไปสามารถลบออกได้โดย iterator
Vasily Liaskovsky

5
คุณสามารถสันนิษฐานได้ว่า แต่เมื่อดูการใช้งานตัววนซ้ำหลายร้อยรายการแล้วมันจะเป็นข้อสันนิษฐานที่ไม่ดี (ฉันยังชอบวิธีนี้คุณแค่ขายมันมากเกินไป)
Brian Goetz

2
@Brian Goetz: การdefaultดำเนินการremoveIfทำให้เกิดสมมติฐานเดียวกัน แต่แน่นอนว่ามีการกำหนดไว้Collectionมากกว่าIterable...
Holger

14

วิธีการแก้ปัญหาโดยตรงจะเรียกในตัวเลือกกลับโดยifPresent(consumer) findFirst()ผู้บริโภครายนี้จะถูกเรียกใช้เมื่อตัวเลือกไม่ว่างเปล่า ข้อดีก็คือจะไม่ทำให้เกิดข้อยกเว้นหากการดำเนินการ find ส่งคืนตัวเลือกว่างเปล่าเช่นรหัสปัจจุบันของคุณจะทำ แทนจะไม่มีอะไรเกิดขึ้น

หากคุณต้องการที่จะกลับค่าเอาออกคุณสามารถถึงผลของการโทร:mapOptionalremove

producersProcedureActive.stream()
                        .filter(producer -> producer.getPod().equals(pod))
                        .findFirst()
                        .map(p -> {
                            producersProcedureActive.remove(p);
                            return p;
                        });

แต่โปรดทราบว่าการremove(Object)ดำเนินการจะข้ามรายการอีกครั้งเพื่อค้นหาองค์ประกอบที่จะลบ หากคุณมีรายการที่มีการเข้าถึงแบบสุ่มเช่นการArrayListสร้างสตรีมบนดัชนีของรายการจะดีกว่าและค้นหาดัชนีแรกที่ตรงกับเพรดิเคต:

IntStream.range(0, producersProcedureActive.size())
         .filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
         .boxed()
         .findFirst()
         .map(i -> producersProcedureActive.remove((int) i));

ด้วยโซลูชันนี้การremove(int)ดำเนินการจะดำเนินการโดยตรงกับดัชนี


3
นี่เป็นพยาธิสภาพสำหรับรายการที่เชื่อมโยง
chrylis -cautiouslyoptimistic-

1
@chrylis โซลูชันดัชนีน่าจะแน่นอน ขึ้นอยู่กับการใช้งานรายการหนึ่งต้องการมากกว่าอีกรายการหนึ่ง ทำการแก้ไขเล็กน้อย
Tunaki

1
@chrylis: ในกรณีที่LinkedListคุณไม่ควรใช้ stream API เนื่องจากไม่มีวิธีแก้ปัญหาโดยไม่ต้องข้ามผ่านอย่างน้อยสองครั้ง แต่ฉันไม่รู้สถานการณ์ในชีวิตจริงที่ข้อได้เปรียบทางวิชาการของรายการที่เชื่อมโยงสามารถชดเชยค่าใช้จ่ายที่แท้จริงได้ LinkedListดังนั้นทางออกที่ง่ายคือการไม่ใช้
Holger

2
โอ้การแก้ไขมากมาย ... ตอนนี้วิธีแก้ปัญหาแรกไม่ได้ให้องค์ประกอบที่ถูกลบออกไปเพราะremove(Object)เพียงส่งคืนการbooleanบอกว่ามีองค์ประกอบที่จะลบหรือไม่
Holger

3
@Marco Stramezzi: น่าเสียดายที่ความคิดเห็นที่อธิบายมันถูกลบออกไป โดยไม่ต้องboxed()คุณจะได้รับOptionalIntซึ่งสามารถmapจากไปint intไม่เหมือนIntStreamไม่มีmapToObjวิธีการ ด้วยboxed(), คุณจะได้รับOptional<Integer>ซึ่งจะช่วยให้mapกับวัตถุโดยพลการเช่นส่งกลับโดยProducerDTO remove(int)โยนจากIntegerที่จะintมีความจำเป็นต้องกระจ่างระหว่างและremove(int) remove(Object)
Holger

9

ใช้สามารถใช้ตัวกรองของ Java 8 และสร้างรายการอื่นหากคุณไม่ต้องการเปลี่ยนรายการเก่า:

List<ProducerDTO> result = producersProcedureActive
                            .stream()
                            .filter(producer -> producer.getPod().equals(pod))
                            .collect(Collectors.toList());

5

ฉันแน่ใจว่านี่จะเป็นคำตอบที่ไม่เป็นที่นิยม แต่ก็ได้ผล ...

ProducerDTO[] p = new ProducerDTO[1];
producersProcedureActive
            .stream()
            .filter(producer -> producer.getPod().equals(pod))
            .findFirst()
            .ifPresent(producer -> {producersProcedureActive.remove(producer); p[0] = producer;}

p[0] จะเก็บองค์ประกอบที่พบหรือเป็นโมฆะ

"เคล็ดลับ" ในที่นี้คือการหลีกเลี่ยงปัญหา "ขั้นสุดท้ายอย่างมีประสิทธิภาพ" โดยใช้การอ้างอิงอาร์เรย์ที่เป็นขั้นสุดท้ายอย่างมีประสิทธิภาพ แต่ตั้งค่าองค์ประกอบแรก


1
กรณีนี้มันไม่ได้แย่ขนาดนั้น แต่ไม่ใช่การปรับปรุงมากกว่าความเป็นไปได้ที่จะโทร.orElse(null)ไปรับProducerDTOหรือnull...
Holger

ในกรณีนั้นมันอาจจะง่ายกว่าที่จะมี.orElse(null)และifไม่มี?
Tunaki

@Holger แต่คุณจะวิงวอนได้อย่างไรremove()โดยใช้orElse(null)?
โบฮีเมียน

1
เพียงแค่ใช้ผล if(p!=null) producersProcedureActive.remove(p);ยังสั้นกว่านิพจน์แลมบ์ดาในการifPresentเรียกของคุณ
Holger

@holger ฉันตีความเป้าหมายของคำถามว่าเป็นการหลีกเลี่ยงการใช้คำสั่งหลาย ๆ ข้อความนั่นคือการแก้ปัญหาแบบ 1 บรรทัด
โบฮีเมียน

4

ด้วยEclipse Collectionsคุณสามารถใช้detectIndexร่วมกับremove(int)java.util.List ใดก็ได้

List<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = Iterate.detectIndex(integers, i -> i > 2);
if (index > -1) {
    integers.remove(index);
}

Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);

หากคุณใช้MutableListประเภทจาก Eclipse Collections คุณสามารถเรียกใช้detectIndexเมธอดได้โดยตรงในรายการ

MutableList<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = integers.detectIndex(i -> i > 2);
if (index > -1) {
    integers.remove(index);
}

Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);

หมายเหตุ: ฉันเป็นผู้กำหนดค่า Eclipse Collections


2

เมื่อเราต้องการรับองค์ประกอบหลายรายการจากรายการไปยังรายการใหม่ (กรองโดยใช้เพรดิเคต) และลบออกจากรายการที่มีอยู่ฉันไม่สามารถหาคำตอบที่เหมาะสมได้จากทุกที่

นี่คือวิธีที่เราสามารถทำได้โดยใช้การแบ่งพาร์ติชัน Java Streaming API

Map<Boolean, List<ProducerDTO>> classifiedElements = producersProcedureActive
    .stream()
    .collect(Collectors.partitioningBy(producer -> producer.getPod().equals(pod)));

// get two new lists 
List<ProducerDTO> matching = classifiedElements.get(true);
List<ProducerDTO> nonMatching = classifiedElements.get(false);

// OR get non-matching elements to the existing list
producersProcedureActive = classifiedElements.get(false);

วิธีนี้ทำให้คุณสามารถลบองค์ประกอบที่กรองออกจากรายการเดิมได้อย่างมีประสิทธิภาพและเพิ่มลงในรายการใหม่

อ้างถึง5.2 Collectors.partitioningByส่วนของบทความนี้


1

ตามที่คนอื่นแนะนำนี่อาจเป็นกรณีการใช้งานสำหรับการวนซ้ำและการวนซ้ำ ในความคิดของฉันนี่เป็นแนวทางที่ง่ายที่สุด หากคุณต้องการแก้ไขรายการในสถานที่จะไม่สามารถพิจารณาการเขียนโปรแกรมที่ใช้งานได้ "จริง" อยู่ดี แต่คุณสามารถใช้Collectors.partitioningBy()เพื่อรับรายการใหม่ที่มีองค์ประกอบที่ตรงตามเงื่อนไขของคุณและรายการใหม่ที่ไม่ได้ แน่นอนว่าด้วยวิธีนี้หากคุณมีองค์ประกอบหลายอย่างที่ตรงตามเงื่อนไขสิ่งเหล่านั้นทั้งหมดจะอยู่ในรายการนั้นไม่ใช่แค่องค์ประกอบแรกเท่านั้น


ดีกว่ามากในการกรองสตรีมและรวบรวมผลลัพธ์ไปยังรายการใหม่
fps

1

ตรรกะด้านล่างเป็นวิธีแก้ปัญหาโดยไม่ต้องแก้ไขรายการเดิม

List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");

List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");

List<String> str3 = str1.stream()
                        .filter(item -> !str2.contains(item))
                        .collect(Collectors.toList());

str1 // ["A", "B", "C", "D"]
str2 // ["D", "E"]
str3 // ["A", "B", "C"]

0

การรวมความคิดเริ่มต้นของฉันและคำตอบของคุณฉันได้รับสิ่งที่ดูเหมือนจะเป็นทางออกสำหรับคำถามของฉันเอง:

public ProducerDTO findAndRemove(String pod) {
    ProducerDTO p = null;
    try {
        p = IntStream.range(0, producersProcedureActive.size())
             .filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
             .boxed()
             .findFirst()
             .map(i -> producersProcedureActive.remove((int)i))
             .get();
        logger.debug(p);
    } catch (NoSuchElementException e) {
        logger.error("No producer found with POD [" + pod + "]");
    }
    return p;
}

มันช่วยให้ลบวัตถุโดยใช้remove(int)ที่ไม่เคลื่อนที่อีกครั้งในรายการ (ตามที่แนะนำโดย @Tunaki) และอนุญาตให้ส่งคืนวัตถุที่ถูกลบไปยังตัวเรียกฟังก์ชัน

ฉันอ่านคำตอบของคุณที่แนะนำให้ฉันเลือกวิธีการที่ปลอดภัยเช่นifPresentแทนที่จะเป็นgetแต่ฉันไม่พบวิธีใช้ในสถานการณ์นี้

มีข้อเสียเปรียบที่สำคัญในการแก้ปัญหาประเภทนี้หรือไม่?

แก้ไขคำแนะนำของ @Holger ดังต่อไปนี้

นี่ควรเป็นฟังก์ชันที่ฉันต้องการ

public ProducerDTO findAndRemove(String pod) {
    return IntStream.range(0, producersProcedureActive.size())
            .filter(i -> producersProcedureActive.get(i).getPod().equals(pod))      
            .boxed()                                                                
            .findFirst()
            .map(i -> producersProcedureActive.remove((int)i))
            .orElseGet(() -> {
                logger.error("No producer found with POD [" + pod + "]"); 
                return null; 
            });
}

2
คุณไม่ควรใช้getและจับข้อยกเว้น นั่นไม่ใช่แค่รูปแบบที่ไม่ดีเท่านั้น แต่ยังอาจทำให้เกิดประสิทธิภาพที่ไม่ดีอีกด้วย วิธีแก้ปัญหาที่สะอาดง่ายกว่านั้นreturn /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
Holger

0

งานคือรับ✶ และ ✶ลบองค์ประกอบออกจากรายการ

p.stream().collect( Collectors.collectingAndThen( Collector.of(
    ArrayDeque::new,
    (a, producer) -> {
      if( producer.getPod().equals( pod ) )
        a.addLast( producer );
    },
    (a1, a2) -> {
      return( a1 );
    },
    rslt -> rslt.pollFirst()
  ),
  (e) -> {
    if( e != null )
      p.remove( e );  // remove
    return( e );    // get
  } ) );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.