Search code examples
javafunctional-programmingjava-stream

Count and print char frequency in text using streams, no maps, no key-value pairs


I'm trying to count and print the frequency of all characters in a text. I want to do this using an Array or ArrayList and no maps with key-value pairs.

The code below gives the preferred result. I want to get rid of the for loop of main And do all the work in method countLetters().

To be clear, I wish to do this in a functional way not using any for loops or if statements. Can this be done? And if so, how?

public class LetterCounter4 {

public static void main(String[] a) { System.out.print("Input text > "); int[] res = countLetters(); for (int i = 0; i < res.length; i++) { if(res[i] != 0){ System.out.println((char) ('a' + i) + " appears " + res[i] + ((res[i] == 1 ? " time" : " times"))); } } } private static int[] countLetters() { return Arrays.stream(new Scanner(System.in).nextLine().toLowerCase() .split("")) .map(s -> s.charAt(0)) .filter(Character::isLetter) .collect(Collector.of( () -> new ArrayList<Integer>(Collections.nCopies(26, 0)), (li, el) -> { Integer oInt = li.get(el - 'a'); li.set(el - 'a', ++oInt); }, (result1, result2) -> { for (int i = 0; i < result1.size(); i++) { Integer temp = result1.get(i); result1.set(i, temp + result2.get(i)); } return result1; })) .stream() .mapToInt(Integer::intValue) .toArray(); } }


Solution

  • The straightforward way is to use .groupingBy(c -> c, Collectors.counting()). The code would be simpler and won't blow up when somebody enters À or some other letter outside [a-zA-Z] range. But that would create a Map, which you say you don't want.

    If you specifically wish to stick with arrays and lists, here's a way to do it:

    public static void main(String[] args) {
        String input = new Scanner(System.in).nextLine();
    
        int[] counts = countLetters(input);
    
        IntStream.range(0, counts.length)
                .filter(i -> counts[i] > 0)
                .forEachOrdered(i -> System.out.printf("%c appears %s %s%n", 
                        'a' + i, 
                        counts[i], 
                        counts[i] > 1 ? "times" : "time"
                ));
    }
    
    public static int[] countLetters(String s) {
        return s.chars() // this is better than stream(split(""))
                .filter(Character::isLetter)  // WRONG to assume that all letters are [a-zA-Z]
                .map(chr -> Character.toLowerCase(chr) - 'a')
                .collect(
                        () -> new int[26], 
                        (ary, i) -> ary[i]++, 
                        (a,b) -> Arrays.setAll(a, i -> a[i] + b[i])
                );
    }