Home > Net >  Count frequency (Specialized) in Java 8 using Lambdas
Count frequency (Specialized) in Java 8 using Lambdas

Time:06-24

I have a list of strings like this: "na","na","na","uk","uk". I want to count the frequency of each element in such a way that if the existing map's value is even, then I will add 1; otherwise 2.

 List<String> streamer = Arrays.asList("na", "na", "na", "uk", "uk");
 Map<String, Integer> m = new HashMap<>();

 for(String s:streamer) {
     if (m.containsKey(s)) {
         if(m.get(s) % 2 == 0)
             m.put(s, m.get(s)   1);
         else
             m.put(s,m.get(s) 2);
     }
     else
         m.put(s,1);
  }
  System.out.println("CUSTOM Frequency::::"   m); 

Now, I want to achieve the exact same thing using streams and lambdas.

All I could do is this:

Map<String, Long>map4 = streamer.stream()
    .collect(Collectors.groupingBy(
        Function.identity(),
        Collectors.counting())); // How can I get custom counting instead of Collectors.counting() ?

CodePudding user response:

Notice that your custom way of counting always results in a count that is one less than twice the result you would have got from counting normally. You are basically counting each thing as 2 things, except the first thing. The only edge case is 0, where both ways of counting produce the same result, but if it is 0, it won't be put in the map anyway, so we don't actually need to handle that.

We could compute the desired result from Collectors.counting() using the collectingAndThen collector:

var frequency = streamer.stream().collect(Collectors.groupingBy(Function.identity(),
    Collectors.collectingAndThen(Collectors.counting(),
        normalCount -> normalCount * 2 - 1)
    ));

CodePudding user response:

I want to count the frequency of each element in such a way that if the existing map's value is even, then I will add 1; otherwise 2

To perform a count in the way you've described, instead of creating a custom collector or nesting three standard collectors one into each other with a combination groupingBy collectionAndThen counting, we can do it by using a single built-in collector toMap().

The code is concise and descriptive:

public static Map<String, Integer> oddEvenCount(Collection<String> strings) {
    
    return strings.stream()
        .collect(Collectors.toMap(
            Function.identity(),                      // key mapper
            str -> 2,                                 // value mapper - executed a key has been encountered first time
            (v1, v2) -> v1 % 2 == 0 ? v1   1 : v1   2 // merging the values: if existing value is even adding `1`, otherwise `2`
        ));
}

main()

public static void main(String[] args) {
    System.out.println(oddEvenCount(Arrays.asList("na","na","na","uk","uk")));
}

Output:

{na=5, uk=3}

A link to Online Demo

CodePudding user response:

Besides @Sweeper's answer to get the expected counting result, if your question also implied how to customize a collect operation, you could use the method of() of the Collector class.

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/stream/Collector.html#of(java.util.function.Supplier,java.util.function.BiConsumer,java.util.function.BinaryOperator,java.util.stream.Collector.Characteristics...)

The method accepts a supplier, an accumulator as a BiConsumer and a combiner as a BinaryOperator. The supplier simply provides the container where to store the result, in your case a Map<String, Long>. The accumulator populates the container by putting or updating an entry with the custom frequency logic. Lastly, The combiner simply merges the sub-results of parallel executions in case the stream is executed as a parallel stream.

List<String> streamer = Arrays.asList("na", "na", "na", "uk", "uk");
Map<String, Long> map4 = streamer.stream()
        .collect(Collector.of(
                HashMap::new,
                (Map<String, Long> map, String s) -> {
                    if (!map.containsKey(s)) {
                        map.put(s, 1L);
                    } else {
                        map.computeIfPresent(s, (String key, Long val) -> val % 2 != 0 ? val   2 : val   1);
                    }
                },
                (Map<String, Long> map1, Map<String, Long> map2) -> {
                    map1.putAll(map2);
                    return map1;
                }
        ));

Output

{na=5, uk=3}
  • Related