Java 8 Stream API for Rubyist
In this post, I will show how to solve the same problem using Java Stream API versus using Ruby. Please check out the solutions written in Ruby and Java to have an overview about Java Stream API.
Introduce the problem
Here is the problem we works on today: For each word in a given sentence, count the number of distinct vowels and return a counting list.
For example, with the input string: "This is an example of sentence.", the program we suppose to write will return an array of [1, 1, 1, 2, 1, 1]. The word "this" has only 1 vowel, which is i, the word "is" has 1 vovel, "a" has 1 vovel, "example" has 3 vowels and the letter "e" appears twice, only one of them is count, that's why the number of distict vowels for the word "example" is 2. The same logic is applied for the word "sentence" because the letter "e" appears 3 times and is counted as 1 letter.
Design the solution
I'm kind of like the idea of breaking the complex operations to multiple smaller steps. Let's define the steps we have to take in order to get back the output array.
- We split the array to a list of words.
- For each word, we extract all the vowels to a list.
- We only collect distinct vowels from each vowels list.
- We get the size of each list and map it to the final result.
With the sample input mentioning in the previous section, here is how it transformed through each steps:
Ruby solution
VOWELS = %w(a e o u i A E O U I)
sentence = "This is an example of sentence"
def count_vowels(sentence)
sentence
.split
.map(&:chars)
.map { |chars| vowels(chars) }
.map(&:uniq)
.map(&:size)
end
def vowels(chars)
chars.select { |char| VOWELS.include?(char) }
end
puts count_vowels(sentence).inspect
Java solution
private Stream<String> vowels(String[] characters) {
List<String> vowels = asList("a", "i", "o", "e", "u");
return Stream.of(characters).filter(vowels::contains);
}
public List<Integer> countVowels(String sentence) {
return Arrays.stream(sentence.split(" "))
.map(word -> word.split(""))
.map(this::vowels)
.map(vowelsStream -> vowelsStream.distinct().collect(toList()))
.map(List::size)
.collect(toList());
}
So how?
The code seems clean for both implementations. The code is self-explained and the syntax is straightforward.
The Java code is written using Java 8 Stream API. This is a huge improvement in Java language. The Stream API is a functional way to write code. It allows developer to focus more on the data flow instead of the implementation details.
The Ruby code is using Enumerable, which is included in the Core Ruby. It is the default way to process a collection of data.
When I implement the solution in Ruby and in Java, I found out that there are some different between the two language. Ruby doesn't require us to separate the workflow to the process stage and the terminal operation like Java. There are couple of things that you may able to do in both stages, which may confuse which is the right stage to put it.
You may wonder how to come with a solution like that, or have any concern about the stream API and Ruby enumerable, please let me know by commenting below.