Kelompokkan dengan menghitung dalam Java 8 stream API


170

Saya mencoba menemukan cara sederhana di Java 8 stream API untuk melakukan pengelompokan, saya keluar dengan cara yang rumit ini!

List<String> list = new ArrayList<>();

list.add("Hello");
list.add("Hello");
list.add("World");

Map<String, List<String>> collect = list.stream().collect(
        Collectors.groupingBy(o -> o));
System.out.println(collect);

List<String[]> collect2 = collect
        .entrySet()
        .stream()
        .map(e -> new String[] { e.getKey(),
                String.valueOf(e.getValue().size()) })
        .collect(Collectors.toList());

collect2.forEach(o -> System.out.println(o[0] + " >> " + o[1]));

Saya menghargai masukan Anda.


1
Apa yang ingin Anda capai di sini?
Keppil

2
Ini adalah kasus yang sangat umum, misalnya saya mengalami kesalahan dalam periode waktu, dan saya ingin melihat beberapa statistik tentang jumlah angka kejadian per setiap hari dalam periode waktu ini.
Muhammad Hewedy

Jawaban:


340

Saya pikir Anda hanya mencari kelebihan yang dibutuhkan orang lain Collectoruntuk menentukan apa yang harus dilakukan dengan masing-masing kelompok ... dan kemudian Collectors.counting()melakukan penghitungan:

import java.util.*;
import java.util.stream.*;

class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("Hello");
        list.add("Hello");
        list.add("World");

        Map<String, Long> counted = list.stream()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

        System.out.println(counted);
    }
}

Hasil:

{Hello=2, World=1}

(Ada juga kemungkinan menggunakan groupingByConcurrentuntuk efisiensi lebih. Sesuatu yang perlu diingat untuk kode asli Anda, jika itu akan aman dalam konteks Anda.)


1
Sempurna! ... dari javadocand then performing a reduction operation on the values associated with a given key using the specified downstream Collector
Muhammad Hewedy

6
Menggunakan Function.identity () (dengan impor statis) alih-alih e -> e membuatnya sedikit lebih baik untuk dibaca: Peta <String, Long> counted = list.stream (). Collect (groupingBy (identity (), menghitung () ));
Kuchi

Hai, saya bertanya-tanya apakah seseorang dapat menjelaskan aspek Peta dari kode Map<String, Long> counted = list.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));, apa yang sebenarnya terjadi pada titik ini dan tautan apa pun dengan penjelasan lebih lanjut terkait dengan topik yang dapat dikirim
Kosong

@Blank: Itu terasa seperti itu akan menjadi yang terbaik sebagai pertanyaan baru, dengan Anda menjelaskan bagian mana dari itu Anda jangan memahami terlebih dahulu. Melewati setiap aspeknya (tidak tahu bagian mana yang tidak Anda mengerti) akan memakan waktu yang sangat lama - lebih banyak waktu daripada yang saya bersedia untuk memberikan jawaban yang sudah lebih dari 5 tahun pada saat ini, ketika sebagian besar dari itu Anda mungkin sudah mengerti.
Jon Skeet

@ JonSkeet Cool, saya akan memasukkannya ke pertanyaan baru, meskipun saya menyoroti aspek yang tidak saya mengerti dalam pertanyaan saya. Oleh karena itu, seluruh potongan kode yang saya tambahkan bersama dengannya.
Kosong

9
List<String> list = new ArrayList<>();

list.add("Hello");
list.add("Hello");
list.add("World");

Map<String, List<String>> collect = list.stream()
                                        .collect(Collectors.groupingBy(o -> o));
collect.entrySet()
       .forEach(e -> System.out.println(e.getKey() + " - " + e.getValue().size()));

8

Berikut adalah contoh untuk daftar Objek

Map<String, Long> requirementCountMap = requirements.stream().collect(Collectors.groupingBy(Requirement::getRequirementType, Collectors.counting()));

8

Berikut adalah beberapa opsi untuk menyelesaikan tugas yang ada.

menggunakan toMap:

list.stream()
    .collect(Collectors.toMap(Function.identity(), e -> 1, Math::addExact));

menggunakan Map::merge:

Map<String, Integer> accumulator = new HashMap<>();
list.forEach(s -> accumulator.merge(s, 1, Math::addExact));

4

Inilah solusi sederhana dari StreamEx

StreamEx.of(list).groupingBy(Function.identity(), Collectors.countingInt());

Kurangi kode boilerplate: collect(Collectors.


1
Apa alasan untuk menggunakannya di atas aliran Java8?
Torsten Ojaperv

1

Jika Anda terbuka untuk menggunakan perpustakaan pihak ketiga, Anda bisa menggunakan Collectors2kelas di Eclipse Collections untuk mengonversi Listmenjadi Bagmenggunakan Stream. A Bagadalah struktur data yang dibangun untuk penghitungan .

Bag<String> counted =
        list.stream().collect(Collectors2.countBy(each -> each));

Assert.assertEquals(1, counted.occurrencesOf("World"));
Assert.assertEquals(2, counted.occurrencesOf("Hello"));

System.out.println(counted.toStringOfItemToCount());

Keluaran:

{World=1, Hello=2}

Dalam kasus ini, Anda cukup collectyang Listlangsung ke Bag.

Bag<String> counted = 
        list.stream().collect(Collectors2.toBag());

Anda juga dapat membuat Bagtanpa menggunakan Streamdengan mengadaptasi Listdengan protokol Koleksi Eclipse.

Bag<String> counted = Lists.adapt(list).countBy(each -> each);

atau dalam kasus khusus ini:

Bag<String> counted = Lists.adapt(list).toBag();

Anda juga bisa langsung membuat Tas.

Bag<String> counted = Bags.mutable.with("Hello", "Hello", "World");

A Bag<String>seperti Map<String, Integer>di dalam yang secara internal melacak kunci dan jumlah mereka. Tetapi, jika Anda meminta Mapkunci yang tidak dikandungnya, itu akan kembali null. Jika Anda meminta Bagkunci yang tidak digunakan occurrencesOf, ia akan mengembalikan 0.

Catatan: Saya pengendara untuk Eclipse Collections.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.