Java 8 lambda mendapatkan dan menghapus elemen dari daftar

91

Diberikan daftar elemen, saya ingin mendapatkan elemen dengan properti tertentu dan menghapusnya dari daftar. Solusi terbaik yang saya temukan adalah:

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

Apakah mungkin untuk menggabungkan get dan remove dalam ekspresi lambda?

Marco Stramezzi
sumber
9
Ini benar-benar tampak seperti kasus klasik saat menggunakan loop dan iterator saja.
chrylis -cautiouslyoptimistic-
1
@chrylis Saya tidak setuju;) Kami begitu terbiasa dengan pemrograman imperatif, sehingga cara lain terdengar terlalu eksotis. Bayangkan jika kenyataannya adalah sebaliknya: kita sangat terbiasa dengan pemrograman fungsional dan paradigma imperatif baru ditambahkan ke Java. Apakah Anda akan mengatakan bahwa ini akan menjadi kasus klasik untuk aliran, predikat, dan pilihan?
fps
9
Jangan menelepon ke get()sini! Anda tidak tahu apakah itu kosong atau tidak. Anda akan membuat pengecualian jika elemen tidak ada di sana. Sebaliknya, gunakan salah satu metode aman seperti ifPresent, orElse, orElseGet, atau orElseThrow.
Brian Goetz
@FedericoPeraltaSchaffner Mungkin masalah selera. Tetapi saya juga akan mengusulkan untuk tidak menggabungkan keduanya. Ketika saya melihat beberapa kode mendapatkan nilai menggunakan aliran saya biasanya berasumsi bahwa operasi dalam aliran bebas dari efek samping. Pencampuran dalam menghapus elemen dapat mengakibatkan kode yang dapat menyesatkan pembaca.
Matthias Wimmer
Hanya untuk memperjelas: Apakah Anda ingin menghapus semua elemen yang di listdalamnya Predicatebenar atau hanya yang pertama (dari kemungkinan nol, satu atau banyak elemen)?
Kedar Mhaswade

Jawaban:

154

Untuk menghapus elemen dari daftar

objectA.removeIf(x -> conditions);

misalnya:

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

OUTPUT: A B C

uma shankar
sumber
saya menyarankan ini sebagai jawaban terbaik
Sergey Pauk
1
Ini sangat rapi. Anda mungkin perlu mengimplementasikan equals / hashCode jika ini tidak dilakukan untuk objek Anda sendiri. (Dalam contoh ini String digunakan yang secara default memilikinya).
bram000
17
IMHO jawabannya tidak mempertimbangkan bagian "dapatkan": removeIfadalah solusi elegan untuk menghilangkan elemen dari koleksi, tetapi tidak mengembalikan elemen yang dihapus.
Marco Stramezzi
Ini adalah mode yang sangat sederhana untuk mengecualikan objek dari java ArrayList. Terima kasih banyak. Bekerja dengan baik untuk saya.
Marcelo Rebouças
3
Ini tidak menjawab pertanyaan itu. Persyaratannya adalah menghapus elemen dari daftar dan dan memindahkan item / item ke daftar baru.
Shanika Ediriweera
35

Meskipun utasnya cukup tua, masih dianggap memberikan solusi - penggunaan Java8.

Manfaatkan removeIffungsi. Kompleksitas waktu adalahO(n)

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

Referensi API: removeIf docs

Asumsi: producersProcedureActiveadalah aList

CATATAN: Dengan pendekatan ini Anda tidak akan bisa mendapatkan item yang dihapus.

asifsid88
sumber
Selain menghapus elemen dari daftar, OP masih menginginkan referensi ke elemen tersebut.
eee
@eee: Terima kasih banyak telah menunjukkannya. Saya melewatkan bagian itu dari pertanyaan awal OP.
asifsid88
hanya untuk dicatat ini akan menghapus semua item yang sesuai dengan kondisi. Tetapi OP tampaknya hanya perlu menghapus item pertama (OP menggunakan findFirst ())
nantitv
18

Pertimbangkan untuk menggunakan iterator java vanilla untuk melakukan tugas:

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

Keuntungan :

  1. Itu jelas dan jelas.
  2. Ini melintasi hanya sekali dan hanya sampai ke elemen yang cocok.
  3. Anda dapat melakukannya di mana saja Iterablebahkan tanpa stream()dukungan (setidaknya yang menerapkan remove()di iteratornya) .

Kekurangan :

  1. Anda tidak dapat melakukannya di tempat sebagai ekspresi tunggal (diperlukan metode tambahan atau variabel)

Adapun

Apakah mungkin untuk menggabungkan get dan remove dalam ekspresi lambda?

jawaban lain dengan jelas menunjukkan bahwa itu mungkin, tetapi Anda harus menyadari

  1. Pencarian dan penghapusan mungkin melintasi daftar dua kali
  2. ConcurrentModificationException mungkin terlempar saat menghapus elemen dari daftar yang sedang diiterasi
Vasily Liaskovsky
sumber
5
Saya suka solusi ini, tetapi perhatikan bahwa ini memiliki satu kerugian serius yang Anda lewatkan: banyak implementasi Iterable memiliki remove()metode yang membuang UOE. (Bukan yang untuk koleksi JDK, tentu saja, tapi menurut saya tidak adil untuk mengatakan "bekerja pada semua Iterable".)
Brian Goetz
Saya pikir kita dapat berasumsi bahwa jika sebuah elemen dapat dihilangkan secara umum , itu dapat dihapus oleh iterator
Vasily Liaskovsky
5
Anda dapat berasumsi demikian, tetapi setelah melihat ratusan implementasi iterator, itu akan menjadi asumsi yang buruk. (Saya masih suka pendekatannya; Anda hanya menjualnya secara berlebihan.)
Brian Goetz
2
@ Brian Goetz: defaultimplementasi removeIfmembuat asumsi yang sama, tetapi, tentu saja, itu didefinisikan pada Collectiondaripada Iterable...
Holger
14

Solusi langsungnya adalah dengan mengaktifkan ifPresent(consumer)Opsional yang dikembalikan olehfindFirst() . Konsumen ini akan dipanggil jika opsionalnya tidak kosong. Manfaatnya juga adalah ia tidak akan memunculkan pengecualian jika operasi find mengembalikan opsional kosong, seperti yang akan dilakukan kode Anda saat ini; sebaliknya, tidak akan terjadi apa-apa.

Jika Anda ingin mengembalikan nilai dihapus, Anda dapat mapdengan Optionaldengan hasil panggilan remove:

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

Tetapi perhatikan bahwa remove(Object)operasi tersebut akan melintasi daftar lagi untuk menemukan elemen yang akan dihapus. Jika Anda memiliki daftar dengan akses acak, seperti sebuah ArrayList, akan lebih baik untuk membuat Aliran di atas indeks daftar dan menemukan indeks pertama yang cocok dengan predikat:

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

Dengan solusi ini, remove(int)operasi beroperasi langsung pada indeks.

Tunaki
sumber
3
Ini patologis untuk daftar tertaut.
chrylis -cautiouslyoptimistic-
1
@chrylis Solusi indeks memang benar. Bergantung pada penerapan daftar, yang satu akan memilih salah satu daripada yang lain. Membuat sedikit pengeditan.
Tunaki
1
@chrylis: jika ada, LinkedListAnda mungkin tidak boleh menggunakan API streaming karena tidak ada solusi tanpa melintasi setidaknya dua kali. Tapi saya tidak tahu skenario kehidupan nyata di mana keuntungan akademis dari daftar tertaut dapat mengimbangi overhead sebenarnya. Jadi solusi sederhananya adalah jangan pernah menggunakan LinkedList.
Holger
2
Oh, begitu banyak hasil edit ... sekarang solusi pertama tidak menyediakan elemen yang dihapus karena remove(Object)hanya mengembalikan yang booleanmemberi tahu apakah ada elemen yang akan dihapus atau tidak.
Holger
3
@Marco Stramezzi: sayangnya, komentar yang menjelaskannya telah dihapus. Tanpa boxed()Anda mendapatkan OptionalIntyang hanya bisa mapdari intsampai int. Tidak seperti IntStream, tidak ada mapToObjmetode. Dengan boxed(), Anda akan mendapatkan Optional<Integer>yang memungkinkan ke mapobjek arbitrer, yaitu ProducerDTOdikembalikan oleh remove(int). Pemeran dari Integerke intperlu untuk membedakan antara remove(int)dan remove(Object).
Holger
9

Gunakan dapat menggunakan filter Java 8, dan membuat daftar lain jika Anda tidak ingin mengubah daftar lama:

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

Saya yakin ini akan menjadi jawaban yang tidak populer, tetapi berhasil ...

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

p[0] akan menahan elemen yang ditemukan atau menjadi nol.

"Trik" di sini adalah mengelak dari masalah "efektif akhir" dengan menggunakan referensi larik yang efektif final, tetapi menyetel elemen pertamanya.

Bohemian
sumber
1
Dalam kasus ini, tidak seburuk itu, tapi bukan peningkatan atas kemungkinan untuk hanya menelepon .orElse(null)untuk mendapatkan ProducerDTOatau null...
Holger
Dalam hal ini, mungkin lebih mudah untuk memiliki .orElse(null)dan memiliki if, bukan?
Tunaki
@Holger tapi bagaimana Anda bisa memanggil remove()juga dengan menggunakan orElse(null)?
Bohemian
1
Gunakan saja hasilnya. if(p!=null) producersProcedureActive.remove(p);itu masih lebih pendek dari ekspresi lambda dalam ifPresentpanggilan Anda .
Holger
@holger Saya menafsirkan tujuan dari pertanyaan sebagai menghindari beberapa pernyataan - yaitu solusi 1 baris
Bohemian
4

Dengan Koleksi Eclipse, Anda dapat menggunakannya detectIndexbersama remove(int)di java.util.List apa pun.

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

Jika Anda menggunakan MutableListtipe dari Eclipse Collections, Anda dapat memanggil detectIndexmetode secara langsung di daftar.

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

Catatan: Saya seorang pelaku untuk Koleksi Eclipse

Donald Raab
sumber
2

Ketika kami ingin mendapatkan beberapa elemen dari Daftar ke dalam daftar baru (filter menggunakan predikat) dan menghapusnya dari daftar yang ada , saya tidak dapat menemukan jawaban yang tepat di mana pun.

Berikut adalah bagaimana kita dapat melakukannya menggunakan partisi 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);

Dengan cara ini Anda secara efektif menghapus elemen yang difilter dari daftar asli dan menambahkannya ke daftar baru.

Lihat 5.2. Collectors.partitioningBy bagian dari artikel ini .

Shanika Ediriweera
sumber
1

Seperti yang disarankan orang lain, ini mungkin kasus penggunaan untuk loop dan iterable. Menurut saya, ini adalah pendekatan yang paling sederhana. Jika Anda ingin mengubah daftar di tempat, ini tidak dapat dianggap sebagai pemrograman fungsional "nyata". Tapi Anda bisa menggunakan Collectors.partitioningBy()untuk mendapatkan daftar baru dengan elemen yang memenuhi kondisi Anda, dan daftar baru yang tidak memenuhi. Tentu saja dengan pendekatan ini, jika Anda memiliki banyak elemen yang memenuhi syarat, semua itu akan ada di daftar itu dan bukan hanya yang pertama.

pengguna140547
sumber
Jauh lebih baik untuk memfilter aliran dan mengumpulkan hasil ke daftar baru
fps
1

Logika di bawah ini adalah solusi tanpa mengubah daftar aslinya

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"]
KimchiMan
sumber
0

Menggabungkan ide awal saya dan jawaban Anda, saya mencapai apa yang tampaknya menjadi solusi untuk pertanyaan saya sendiri:

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

Ini memungkinkan menghapus objek menggunakan remove(int)yang tidak melintasi lagi daftar (seperti yang disarankan oleh @Tunaki) dan memungkinkan mengembalikan objek yang dihapus ke pemanggil fungsi.

Saya membaca jawaban Anda yang menyarankan saya untuk memilih metode aman seperti ifPresentdaripadaget tetapi saya tidak menemukan cara untuk menggunakannya dalam skenario ini.

Apakah ada kelemahan penting dalam solusi semacam ini?

Edit mengikuti saran @Holger

Ini harus menjadi fungsi yang saya butuhkan

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; 
            });
}
Marco Stramezzi
sumber
2
Anda tidak boleh menggunakan getdan menangkap pengecualian. Itu tidak hanya gaya yang buruk tetapi juga dapat menyebabkan kinerja yang buruk. Solusi bersih bahkan lebih sederhana,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
Holger
0

tugasnya adalah: mendapatkan ✶ dan ✶ menghapus elemen dari daftar

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