Java – how to improve on this function (Java 8 streams)

Tags: , ,



I’ve coded a function that finds the unique characters in a string using Java 8 streams, based on an example here.

To me, it’s pretty non-intuitive, maybe because I’m still learning streams. Is there any way to make this more readable?

Here’s the code:

    public static void main(String[] args) {

        String str = "aabcddeffg";

        char[] charArray = str.toCharArray();

        List<String> strList = new ArrayList<>();
        for(int i=0; i< charArray.length; i++){
            String myChar = String.valueOf(charArray[i]);
            strList.add(myChar);
        }

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

        myMap.forEach((k, v) -> {
            if (v == 1) {
                System.out.println(k);
            }
        });

    }
}

Answer

For calculating the actual frequencies, you’re pretty much at a minimal example already!

Map<Integer, Long> charFrequency = strList.stream() //Stream<String>
            .flatMapToInt(String::chars)  //Stream<IntStream> -> IntStream
            .boxed() //IntStream -> Stream<Integer>
            .collect(Collectors.groupingBy( //Map<K, V>
                    Function.identity(), //K == our integers
                    Collectors.counting() //V == the number of them
            ));

charFrequency.entrySet().stream() //Stream<Map.Entry<Integer, Long>>
            .filter(ent -> ent.getValue() == 1) //only entries with value of 1
            .mapToInt(Map.Entry::getKey) //Stream<Entry> -> IntStream
            .forEach(c -> {
                System.out.println("Found unique character: " + ((char) c));
            });

And for doing it for a single string, it’s even easier (you save the conversions):

Map<Integer, Long> charFrequency = someString.chars() //Stream<Integer>
            .collect(Collectors.groupingBy( //Map<K, V>
                    Function.identity(), //K == our integers
                    Collectors.counting() //V == the number of them
            ));

To that end, I would ensure that your code is simply consistent and readable. Use consistent indentation and comment how the stream steps per line, for example.

Edit: I’ve left the below (old) answer just to be informative for the OP, but it doesn’t answer the actual question.

Well, there’s always Stream#distinct.

Calcuating the “distinct” (not unique) characters:

List<Integer> distinctChars = strList.stream() //Stream<String>
        .flatMapToInt(String::chars)  //Stream<IntStream> -> IntStream
        .distinct() //unique IntStream
        .boxed() //unique Stream<Integer>
        .collect(Collectors.toList()); //List<Integer>

distinctChars.forEach(c -> {
    System.out.println("Found distinct char: " + ((char) (int) c));
});

If you want to avoid collecting it, you can also avoid all the hassle around the type boxing:

strList.stream() //Stream<String>
        .flatMapToInt(String::chars)  //Stream<IntStream> -> IntStream
        .distinct() //unique IntStream
        .forEach(c -> {
            System.out.println("Found distinct char: " + ((char) c));
        });


Source: stackoverflow