I am new to functional programming, and I am trying to get better.
Currently, I am experimenting with some code that takes on the following basic form:
private static int myMethod(List<Integer> input){ Map<Integer,Long> freq = input .stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); return (int) freq .keySet() .stream() .filter(key-> freq.containsKey(freq.get(key))) .count(); }
First a hashmap is used to get the frequency of each number in the list. Next, we sum up the amount of keys which have their values that also exist as keys in the map.
What I don’t like is how the two streams need to exist apart from one another, where a HashMap is made from a stream only to be instantly and exclusively consumed by another stream.
Is there a way to combine this into one stream? I was thinking something like this:
private static int myMethod(List<Integer> input){ return (int) input .stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .keySet() .stream() .filter(key-> freq.containsKey(freq.get(key))) .count(); }
but the problem here is there is no freq map to reference, as it is used as part of the pipeline, so the filter cannot do what it needs to do.
In summary, I don’t like that this collects to a hashmap only then to convert back into a keyset. Is there a way to “streamline” (pun intended) this operation to
- Not be going back and forth from stream and hashmap
- Reference itself in a way without needing to declare a separate map before the pipeline.
Thank you!
Advertisement
Answer
Your keySet is nothing but effectively a HashSet
formed of your input
. So, you should make use of temporary storage such that:
Set<Integer> freq = new HashSet<>(input);
and further count, filter based on values in a single stream pipeline as
return (int) input .stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .values() // just using the frequencies evaluated .stream() .filter(count -> freq.contains(count.intValue())) .count();