Counting the number of occurrences of letters in a string using a stream (Stream)

There is a certain string(for example, "Some example"). You need to determine the number of occurrences of each letter in a string using the Stream stream and at the same time without using loops, if and other things(only recursion, Stream streams and its methods like map, reduce, filter).

I made an array of characters from the string, which I then turned into a list using the recursive toCharList method.

String text = "Some example".toLowerCase.replaceAll("\\s", "")
List<Character> textInChar = toCharList(text.toCharArray(), 0);

static public List<Character> toCharList(char[] textChars, int i) {

    if(textChars.length == i) return new ArrayList<>();

    List<Character> ret =  new ArrayList<>();
    ret.add(textChars[i]);
    ret.addAll(toCharList(textChars, i + 1));


    return ret;
}

Then, from this list, I created a hash map using the overloaded toCharMap method. So so I got a list of all the non-repeating letters that are in the string.

Map<Character, Integer>  countChar = toCharMap(textInChar);

public static Map<Character, Integer> toCharMap(List<Character> l) {
    return toCharMap(l.iterator());
}


public static Map<Character, Integer> toCharMap(Iterator it) {
    if (!it.hasNext()) return new HashMap<>();

    Map<Character, Integer> ret = new HashMap<>();
    ret.put((Character)it.next(), 0);
    ret.putAll(toCharMap(it));

    return ret;
}

But now how to count them in the stream, for example, using filter and reduce?

Author: Nick Volynkin, 2016-01-18

3 answers

1) It is better to get an array of characters from a string like this:

String text = ("Some example").toLowerCase().replaceAll("\\s", "");
List<Character> textInChar = Chars.asList(text.toCharArray());

Or:

textInChar = text.chars().mapToObj(e->(char)e).collect(Collectors.toList());

2) The hash map of non-repeating characters is better to get then so:

Map<Character, Integer> countChar = textInChar.stream().collect(HashMap::new, (m, c) -> {
            m.put(c, 1);
            m.put(c, 1);
        }, HashMap::putAll);

And I would solve the whole problem like this:

textInChar = text.chars().mapToObj(e->(char)e).collect(Collectors.toList());
Map<Character, Integer> countChar = textInChar.stream().collect(HashMap::new, (m, c) -> {
            if(m.containsKey(c))
                m.put(c, m.get(c) + 1);
            else
                m.put(c, 1);
        }, HashMap::putAll);

Check it out:

countChar.forEach( (k, v) -> LOG.debug(k + " -> " + v));

Outputs:

p -> 1
a -> 1
s -> 1
e -> 3
x -> 1
l -> 1
m -> 2
o -> 1

UPD:

if(m.containsKey(c))
    m.put(c, m.get(c) + 1);
else
    m.put(c, 1);

Can be shortened to:

m.put(c, m.containsKey(c) ? (m.get(c) + 1) : 1);
 5
Author: Suvitruf - Andrei Apanasik, 2016-01-18 23:34:17

To group Stream elements, you can use the collector Collectors.groupingBy. In addition to the option with a single aggrum (classifier function), it has a variant that accepts another collector, with which the reduction of groups to a single value will be performed:

// Character::valueOf принимает char, а поток у нас из int
String output = "Some example".chars().mapToObj( ch -> new Character( (char)ch ) )
        // сгруппировать по символам, внутри группы подсчитать количество
        .collect( Collectors.groupingBy( Function.identity(), Collectors.counting() ) )
        // группировка возвращает Map<Character, Integer> с количеством разных символов
        .entrySet().stream()
        // если надо выбрать только встречающиеся 1 раз
        //.filter( entry -> entry.getValue() == 1 )
        // раз forEach плохо :)
        //.forEach( entry -> System.out.printf( "'%s' -> %d%n", entry.getKey(), entry.getValue() ) );
        .map( entry -> String.format( "'%s' -> %d%n", entry.getKey(), entry.getValue() ) )
        .collect( Collectors.joining() );

System.out.println( output );
 4
Author: zRrr, 2016-01-19 03:23:00
String string = "Some example";

Map<Character, AtomicInteger> result = IntStream
    .range(0, string.length())
    // .parallel() // can be parallel for very big strings
    .mapToObj(string::charAt)
    .collect(
        Collectors.toMap(
            Function.identity(),
            c -> new AtomicInteger(1),
            (count1, count2) -> {
                count1.addAndGet(count2.getAndSet(0));
                return count1;
            }
        )
    );

For comparison, the imperative implementation:

String string = "Some example";

Map<Character, AtomicInteger> map = new HashMap<>();
for (int i = 0; i < string.length(); i++) {
    map
        .computeIfAbsent(string.charAt(i), character -> new AtomicInteger())
        .incrementAndGet();
}

Which is much more productive in any case

 0
Author: Alexey, 2020-08-01 18:33:08