Groovy Functional Programming - Filtering with findAll
In my previous post, I wrote about map
transformation. Similarly, another common operation is filtering values from a collection that satisfy a condition. We refer to the condition as a predicate.
Consider the following list of numbers.
def numbers = [1, 2, 3, 4, 5, 6, 7]
Let's write a function to find the even numbers in the above list of numbers.
def filterEvenNumbers(numbers) {
def evenNumbers = []
for(number in numbers) {
if(number % 2 == 0){
evenNumbers << number
}
}
evenNumbers
}
println filterEvenNumbers(numbers) // [2, 4, 6]
Similarly let's write another function to find the numbers divisible by 3
def filterNumbersDivisibleByThree(numbers) {
def numbersDivisibleByThree = []
for(number in numbers) {
if(number % 3 == 0){
numbersDivisibleByThree << number
}
}
numbersDivisibleByThree
}
println filterNumbersDivisibleByThree(numbers) // [3, 6]
That's a lot of duplicate code. If you observe closely, the only difference between those functions filterEvenNumbers
and filterNumbersDivisibleByThree
is the criterion for filtering(the logic inside if condition). Let's attempt to pull the filtering logic out and reuse the looping logic.
def isEven = { number ->
number % 2 == 0
}
def isDivisibleByThree = { number ->
number % 3 == 0
}
The reason why I chose closures instead of functions is because the closures can be passed as arguments to other functions and closures.
You may take a look at my post on higher order functions, if you are not already familiar with the concept.
Now the code to filter numbers based on the above conditions would look as follows.
def filter(numbers, condition) {
def filtered = []
for(number in numbers) {
if(condition(number)) {
filtered << number
}
}
filtered
}
println filter(numbers, isEven) // [2, 4, 6]
println filter(numbers, isDivisibleByThree) // [3, 6]
You will be pleasantly surprised to know that Groovy already provides that filter function. It is called findAll
. Let's move aways from our attempt to reinvent the wheel, and refactor the code to use findAll
instead.
println numbers.findAll(isEven) // [2, 4, 6]
println numbers.findAll(isDivisibleByThree) // [3, 6]
You may want to take a look at the slides from my FunctionalConf talk for more examples.