3

I wish to create a int[] of count for a particular String (comprising of only lowercase English Alphabets) using Java 8 stream API. Where arr[i] denotes the count of i-th character of English dictionary (e.g. arr[0] = count of 'a' in String str while arr[2] = count of 'c' in String str. This can be simply done by:

int[] arr = new int[26];
for(char c : str.toCharArray())
       arr[c-'a']++;

Or using IntSream in the 2nd way:

int[] arr = IntStream.range('a','z'+1).map(i -> (int)str.chars().filter(c -> c == i).count()).toArray();

But the problem with the second approach is that the String is traversed 26 times for each of the characters from 'a' to 'z'

Can you suggest a better way of achieving the same using java8-stream API?

PS: I know this can be done using Map but I need int[]

1
  • Your iterative approach is much better. Don't use streams. Keep what you have. Commented Apr 15, 2020 at 4:08

2 Answers 2

6
int[] r = str.chars()
             .boxed()
             .reduce(new int[26], 
                     (a, c) -> { ++a[c - 'a']; return a; }, 
                     (a1, a2) -> a1);

You know the former is simpler and better. My answer just proves it's feasible with the Stream API, and doesn't suggest that you should go with it. Personally, I would choose the map approach as the most intuitive one.

As pointed out by @Holger, collect is a better option here

str.chars()
   .map(c -> c - 'a')
   .collect(() -> new int[26], 
            (a, i)-> a[i]++, 
            (a1, a2) -> /* left as an exercise to the reader*/);
Sign up to request clarification or add additional context in comments.

3 Comments

I would throw .filter(i -> i >= 'a' && i <= 'z') after .chars() just in case someone calls it with out-of-range characters in str, to avoid the potential out of bounds exception.
@DavidConrad I agree, but the OP seems to be confident with the new int[26], maybe str is being validated before this
Modifying incoming arguments in a reduction function is an abuse of the API. Obviously, this would break with a parallel stream. This is a perfect use case for collect, which can even be done without boxing, str.chars().map(c -> c - 'a').collect(() -> new int[26], (a,i)->a[i]++, (a1, a2) -> /* left as an exercise to the reader*/)
2

if you want to use streams and keep the iterative approach, you could do it as well like this:

final int count[] = new int[26];
test.chars().forEach(c -> count[c-'a']++);

2 Comments

I found this working. Can you explain why is this not violation of the "Effective final" clause of lambda functions?
As arrays are objects in java, the count array is de facto final; its reference is not changed during the iteration ie. it is not assigned to a new object. it stays final even if you change values (members) of an object.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.