2015-03-18 23 views
36

Cách đếm tần suất của các từ trong Danh sách trong Java 8?Số tần số từ Java 8

List <String> = Lists.newArrayList("hello", "bye", "ciao", "bye", "ciao"); 

Kết quả phải:

{ciao=2, hello=1, bye=2} 

Trả lời

55

tôi muốn chia sẻ những giải pháp tôi thấy vì lúc đầu tôi dự kiến ​​sẽ sử dụng bản đồ-and-giảm phương pháp, nhưng đó là một chút khác nhau.

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

Hoặc cho các giá trị Integer:

Map<String, Integer> collect = 
     wordsList.stream().collect(groupingBy(Function.identity(), summingInt(e -> 1))); 

EDIT

tôi thêm làm thế nào để sắp xếp các bản đồ theo giá trị:

LinkedHashMap<String, Long> countByWordSorted = collect.entrySet() 
      .stream() 
      .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) 
      .collect(Collectors.toMap(
        Map.Entry::getKey, 
        Map.Entry::getValue, 
        (v1, v2) -> { 
         throw new IllegalStateException(); 
        }, 
        LinkedHashMap::new 
      )); 
+13

Tại sao 'summingInt (e -> 1) '? Chỉ cần sử dụng ['counting()'] (http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#counting--). Kết quả sẽ là một 'Bản đồ 'sau đó nhưng điều đó sẽ không bị tổn thương. Btw. bạn có thể thay thế 'e-> e' bằng' Function.identity() '(nhưng bạn không phải). – Holger

15

(LƯU Ý: Xem các chỉnh sửa bên dưới)

Để thay thế cho Mounas answer, đây là một cách tiếp cận mà từ đếm song song:

import java.util.Arrays; 
import java.util.List; 
import java.util.Map; 
import java.util.stream.Collectors; 

public class ParallelWordCount 
{ 
    public static void main(String[] args) 
    { 
     List<String> list = Arrays.asList(
      "hello", "bye", "ciao", "bye", "ciao"); 
     Map<String, Integer> counts = list.parallelStream(). 
      collect(Collectors.toConcurrentMap(
       w -> w, w -> 1, Integer::sum)); 
     System.out.println(counts); 
    } 
} 

EDIT Để đối phó với những nhận xét, tôi chạy một thử nghiệm nhỏ với JMH, so sánh toConcurrentMap và cách tiếp cận groupingByConcurrent, với các kích thước danh sách đầu vào khác nhau và các từ ngẫu nhiên có độ dài khác nhau. Thử nghiệm này cho thấy cách tiếp cận toConcurrentMap nhanh hơn. Khi xem xét các cách tiếp cận này khác nhau như thế nào "dưới mui xe", thật khó để dự đoán một cái gì đó như thế này.

Là phần mở rộng hơn nữa, dựa trên các nhận xét thêm, tôi đã mở rộng thử nghiệm để bao gồm tất cả bốn kết hợp của toMap, groupingBy, nối tiếp và song song.

Kết quả vẫn còn đó cách tiếp cận toMap là nhanh hơn, nhưng bất ngờ (ít nhất, đối với tôi) các phiên bản "đồng thời" trong cả hai trường hợp là chậm hơn so với các phiên bản nối tiếp ...:

   (method) (count) (wordLength) Mode Cnt  Score Error Units 
     toConcurrentMap  1000   2 avgt 50 146,636 ± 0,880 us/op 
     toConcurrentMap  1000   5 avgt 50 272,762 ± 1,232 us/op 
     toConcurrentMap  1000   10 avgt 50 271,121 ± 1,125 us/op 
       toMap  1000   2 avgt 50 44,396 ± 0,541 us/op 
       toMap  1000   5 avgt 50 46,938 ± 0,872 us/op 
       toMap  1000   10 avgt 50 46,180 ± 0,557 us/op 
      groupingBy  1000   2 avgt 50 46,797 ± 1,181 us/op 
      groupingBy  1000   5 avgt 50 68,992 ± 1,537 us/op 
      groupingBy  1000   10 avgt 50 68,636 ± 1,349 us/op 
groupingByConcurrent  1000   2 avgt 50 231,458 ± 0,658 us/op 
groupingByConcurrent  1000   5 avgt 50 438,975 ± 1,591 us/op 
groupingByConcurrent  1000   10 avgt 50 437,765 ± 1,139 us/op 
     toConcurrentMap 10000   2 avgt 50 712,113 ± 6,340 us/op 
     toConcurrentMap 10000   5 avgt 50 1809,356 ± 9,344 us/op 
     toConcurrentMap 10000   10 avgt 50 1813,814 ± 16,190 us/op 
       toMap 10000   2 avgt 50 341,004 ± 16,074 us/op 
       toMap 10000   5 avgt 50 535,122 ± 24,674 us/op 
       toMap 10000   10 avgt 50 511,186 ± 3,444 us/op 
      groupingBy 10000   2 avgt 50 340,984 ± 6,235 us/op 
      groupingBy 10000   5 avgt 50 708,553 ± 6,369 us/op 
      groupingBy 10000   10 avgt 50 712,858 ± 10,248 us/op 
groupingByConcurrent 10000   2 avgt 50 901,842 ± 8,685 us/op 
groupingByConcurrent 10000   5 avgt 50 3762,478 ± 21,408 us/op 
groupingByConcurrent 10000   10 avgt 50 3795,530 ± 32,096 us/op 

tôi không quá kinh nghiệm với JMH, có lẽ tôi đã làm điều gì sai ở đây - đề xuất và sửa được chào đón:

import java.util.ArrayList; 
import java.util.List; 
import java.util.Map; 
import java.util.Random; 
import java.util.concurrent.TimeUnit; 
import java.util.function.Function; 
import java.util.stream.Collectors; 

import org.openjdk.jmh.annotations.Benchmark; 
import org.openjdk.jmh.annotations.BenchmarkMode; 
import org.openjdk.jmh.annotations.Mode; 
import org.openjdk.jmh.annotations.OutputTimeUnit; 
import org.openjdk.jmh.annotations.Param; 
import org.openjdk.jmh.annotations.Scope; 
import org.openjdk.jmh.annotations.Setup; 
import org.openjdk.jmh.annotations.State; 
import org.openjdk.jmh.infra.Blackhole; 

@State(Scope.Thread) 
public class ParallelWordCount 
{ 

    @Param({"toConcurrentMap", "toMap", "groupingBy", "groupingByConcurrent"}) 
    public String method; 

    @Param({"2", "5", "10"}) 
    public int wordLength; 

    @Param({"1000", "10000" }) 
    public int count; 

    private List<String> list; 

    @Setup 
    public void initList() 
    { 
     list = createRandomStrings(count, wordLength, new Random(0)); 
    } 

    @Benchmark 
    @BenchmarkMode(Mode.AverageTime) 
    @OutputTimeUnit(TimeUnit.MICROSECONDS) 
    public void testMethod(Blackhole bh) 
    { 

     if (method.equals("toMap")) 
     { 
      Map<String, Integer> counts = 
       list.stream().collect(
        Collectors.toMap(
         w -> w, w -> 1, Integer::sum)); 
      bh.consume(counts); 
     } 
     else if (method.equals("toConcurrentMap")) 
     { 
      Map<String, Integer> counts = 
       list.parallelStream().collect(
        Collectors.toConcurrentMap(
         w -> w, w -> 1, Integer::sum)); 
      bh.consume(counts); 
     } 
     else if (method.equals("groupingBy")) 
     { 
      Map<String, Long> counts = 
       list.stream().collect(
        Collectors.groupingBy(
         Function.identity(), Collectors.<String>counting())); 
      bh.consume(counts); 
     } 
     else if (method.equals("groupingByConcurrent")) 
     { 
      Map<String, Long> counts = 
       list.parallelStream().collect(
        Collectors.groupingByConcurrent(
         Function.identity(), Collectors.<String> counting())); 
      bh.consume(counts); 
     } 
    } 

    private static String createRandomString(int length, Random random) 
    { 
     StringBuilder sb = new StringBuilder(); 
     for (int i = 0; i < length; i++) 
     { 
      int c = random.nextInt(26); 
      sb.append((char) (c + 'a')); 
     } 
     return sb.toString(); 
    } 

    private static List<String> createRandomStrings(
     int count, int length, Random random) 
    { 
     List<String> list = new ArrayList<String>(count); 
     for (int i = 0; i < count; i++) 
     { 
      list.add(createRandomString(length, random)); 
     } 
     return list; 
    } 
} 

giờ chỉ tương tự như đối với trường hợp nối tiếp của một danh sách với 10000 yếu tố, và 2 chữ cái. Có thể đáng để kiểm tra xem liệu các kích thước danh sách lớn hơn hay không, các phiên bản đồng thời cuối cùng cũng hoạt động tốt hơn các phiên bản nối tiếp, nhưng hiện tại không có thời gian cho một điểm chuẩn chi tiết khác với tất cả các cấu hình này.

+0

Tôi đoán, 'Collectors.groupingByConcurrent (w-> w, Collectors.counting())' sẽ hiệu quả hơn. – Holger

+0

@Holger Tôi đã thêm một EDIT về điều này. – Marco13

+0

Bạn cũng nên quan tâm đến số từ * bằng *. Sự tranh chấp cho một mục nhập bản đồ có thể có tác động đáng kể. Việc đếm hàng ngàn từ riêng biệt có thể hoạt động mà không có bất kỳ tranh chấp nào trong 'Bản đồ đồng thời' của Java 8 mặc dù tôi sẽ không gọi lưu trữ '1' "đếm". Vì vậy, đếm hàng ngàn lần xuất hiện của cùng một từ có thể đưa ra một hình ảnh khác nhau… – Holger

3

Nếu bạn sử dụng Eclipse Collections, bạn chỉ có thể chuyển đổi List thành Bag.

Bag<String> words = Lists.mutable.with("hello", "bye", "ciao", "bye", "ciao").toBag(); 
Assert.assertEquals(2, words.occurrencesOf("ciao")); 
Assert.assertEquals(1, words.occurrencesOf("hello")); 
Assert.assertEquals(2, words.occurrencesOf("bye")); 

Mã này sẽ hoạt động với Java 5 - 8.

Lưu ý: Tôi là một người có duyên cho Eclipse Collections

2

tôi sẽ trình bày các giải pháp ở đây mà tôi đã thực hiện (một với nhóm là tốt hơn nhiều :)).

static private void test0(List<String> input) { 
    Set<String> set = input.stream() 
      .collect(Collectors.toSet()); 
    set.stream() 
      .collect(Collectors.toMap(Function.identity(), 
        str -> Collections.frequency(input, str))); 
} 

Chỉ cần tôi 0.02 $

+0

Tôi đã biết về Collections.frequency (đầu vào, str). Cảm ơn về thông tin bạn vừa nhập. – Sam

0

Một 2 phần trăm của tôi, đưa ra một mảng:

import static java.util.stream.Collectors.*; 

String[] str = {"hello", "bye", "ciao", "bye", "ciao"};  
Map<String, Integer> collected 
= Arrays.stream(str) 
     .collect(groupingBy(Function.identity(), 
        collectingAndThen(counting(), Long::intValue))); 
0

Đây là một cách để tạo ra một bản đồ tần số sử dụng chức năng bản đồ.

List<String> words = Stream.of("hello", "bye", "ciao", "bye", "ciao").collect(toList()); 
Map<String, Integer> frequencyMap = new HashMap<>(); 

words.forEach(word -> 
     frequencyMap.merge(word, 1, (v, newV) -> v + newV) 
); 

System.out.println(frequencyMap); // {ciao=2, hello=1, bye=2} 

Hoặc

words.forEach(word -> 
     frequencyMap.compute(word, (k, v) -> v != null ? v + 1 : 1) 
); 
0

Tìm mục thường xuyên nhất trong bộ sưu tập, với Generics:

private <V> V findMostFrequentItem(final Collection<V> items) 
{ 
    return items.stream() 
     .filter(Objects::nonNull) 
     .collect(Collectors.groupingBy(Functions.identity(), Collectors.counting())).entrySet().stream() 
     .max(Comparator.comparing(Entry::getValue)) 
     .map(Entry::getKey) 
     .orElse(null); 
} 

Tính mục tần số:

private <V> Map<V, Long> findFrequencies(final Collection<V> items) 
{ 
    return items.stream() 
     .filter(Objects::nonNull) 
     .collect(Collectors.groupingBy(Functions.identity(), Collectors.counting())); 
} 
Các vấn đề liên quan