Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Collectors.collectingAndThen with Collectors.groupingBy

Given the following list as example :

List<String> input = List.of("FOO", "FOO", "FOO", "FOO", "FOO", "BAR", "BAR", "BAZ", "BAZ", "BAZ", "DOO", "DOO"); 

I need to get the relative Frequency of each element and print it formatted as a string. To do so I have used 2 steps, first create a frequency map and then calculate relative percentage and format:

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

Map<String, String> relativeFrequency2 = relativeFrequency.entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getKey,
                entry -> String.format ( "%.02f%%", entry.getValue() * 100.  / input.size() )));

System.out.println(relativeFrequency2);

and get the result

{BAR=16.67%, DOO=16.67%, FOO=41.67%, BAZ=25.00%}

I was told to refactor the above and do it in one go using Collectors.collectingAndThen, but I can't seem to find the right syntax (compile error Operator '*' cannot be applied to '<lambda parameter>', 'double'). Can someone help to correct the following to get the same result as above?

Map<String, String> relativeFrequency3 = 
input.stream().collect(Collectors.collectingAndThen(Collectors.groupingBy(Function.identity(), Collectors.counting(),
                total -> String.format ( "%.02f%%", total * 100.  / input.size() ))
));
like image 268
bbKing Avatar asked Oct 29 '25 17:10

bbKing


2 Answers

You were close.

List<String> input =
        List.of("FOO", "FOO", "FOO", "FOO", "FOO", "BAR",
                "BAR", "BAZ", "BAZ", "BAZ", "DOO", "DOO");
  • the first argument to collectingAndThen would be counting() to get the frequency.
  • then you process that frequency by converting it to a formatted percentage.
Map<String, String> map = input.stream().collect(Collectors
        .groupingBy(a -> a, Collectors.collectingAndThen(
                Collectors.counting(),
                count -> "%.2f%%".formatted((double) count
                        / input.size() * 100))));
        
map.entrySet().forEach(System.out::println);

printing each entry set yields

BAR=16.67%
DOO=16.67%
FOO=41.67%
BAZ=25.00%
like image 89
WJS Avatar answered Nov 01 '25 06:11

WJS


import java.util.*;
import java.util.stream.*;
import java.util.function.Function;

class Main {
    public static void main(String args[]) {
        var input = List.of("FOO", "FOO", "FOO", "FOO", "FOO", "BAR", "BAR", "BAZ", "BAZ", "BAZ", "DOO", "DOO");
        var out = input.stream()
            .collect(Collectors.groupingBy(
                w -> w,
                Collectors.collectingAndThen(
                    Collectors.summingInt(w -> 1),
                    s -> String.format("%.02f%%", s * 100. / input.size()))
            ));
        System.out.println(out);
    }
}
like image 29
Abhijit Sarkar Avatar answered Nov 01 '25 07:11

Abhijit Sarkar