In this short article we’ll take a look at how can we filter collections in Kotlin. We will start by checking how to filter a list in Kotlin, let’s start!
filter
The most basic form of filtering is the filter
method. This method will only include those elements fulfilling the predicate and will return a new collection including them. You’ll be able to understand this easily with a simple example.
We’re going to write a test that filters numbers lower or equal to ten, let’s see how can we achieve that.
class FilterCollectionsTest {
@Test
internal fun `filters out numbers greater than 10`() {
val numbers = listOf(10, 1, 7, 99, 24, 10, 7, 3, 33)
val filtered = numbers.filter { num -> num <= 10 }
assertThat(filtered).containsExactly(10, 1, 7, 10, 7, 3)
}
}
In this test we define a List of Int with different numbers between 1 and 100 and allocate them in an inmutable variable called numbers
. As you can see, when we filter elements from this list we allocate the new list into a list called filtered
. The filter
method accepts a predicate or condition expressed in the form of a lambda.
Let’s take a look at the implementation of filter
method:
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
There are few things that stand out in this implementation. First, we can see how the predicate is expressed as a lambda which accepts an element of generic type and returns a boolean. We can also see how Kotlin makes use of Kotlin extensions to make filter
method available in our collections. The last thing we can notice is that filter
method calls filterTo
method to filter the elements into a new ArrayList instance. This is precisely the method we’re going to look at next!
filterTo
The filterTo
behaves similarly to filter
method, with the difference being in the fact that the result of the filtering will be appended to a destination collection. Let’s see how with a brief example.
@Test
internal fun `includes elements lower than 10 into an existing list`() {
val existing = mutableListOf(-1, 0, -2, -3)
val numbers = listOf(10, 1, 7, 99, 24, 10, 7, 3, 33)
val filtered = numbers.filterTo(existing) { num -> num <= 10 }
assertThat(filtered).containsExactly(-1, 0, -2, -3, 10, 1, 7, 10, 7, 3)
}
In the example above we define a mutable list with some numbers lower than 1. We then use filterTo
method to filter those numbers below 10 from our original list and get the appended to the first list.
Notice how a mutable collection is required for our destination list, that means that our code won’t compile if we try to pass an immutable collection. This makes this code much safer, it’s great that we get the error at compile time and not at runtime!
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
There are some situations where we’d need to get the elements that don’t fulfil a predicate, although we could also use filter
and filterTo
, in some cases it could be clearer to use filterNot
.
filterNot
The method filterNot
will do the opposite of filter
method, it’ll exclude the elements fulfilling the predicate.
Let’s see how to use this in an example.
@Test
fun `should exclude positive numbers`() {
val numbers = listOf(10, 0, -1, 7, 99, -7, 10, 7, -3, 33)
val filtered = numbers.filterNot { num -> num > 0 }
assertThat(filtered).containsExactly(0, -1, -7, -3)
}
We have excluded every positive number from an initial list of integers, resulting in a list that contains zero and any existing negative numbers.
If we look at the implementation of filterNot
, we’ll notice that it uses filterNotTo
to implement this filter.
public inline fun <T> Iterable<T>.filterNot(predicate: (T) -> Boolean): List<T> {
return filterNotTo(ArrayList<T>(), predicate)
}
We’ll take a look at that method now.
filterNotTo
The method filterNotTo
can be used to append the resulting elements to an existing collection. The way that filterNot
uses this method is by appending the elements to an empty ArrayList
in the case of a list.
We can have a test like the following to demonstrate how it works.
@Test
fun `should exclude positive numbers and append them to existing list`() {
val existing = mutableListOf(-11, -22, -30, -21)
val numbers = listOf(10, 0, -1, 7, 99, -7, 10, 7, -3, 33)
val filtered = numbers.filterNotTo(existing) { num -> num > 0 }
assertThat(filtered).containsExactly(-11, -22, -30, -21, 0, -1, -7, -3)
}
In this example we have an existing
list with negative numbers and we want to append the non-positive numbers of another list called numbers
to it. The resulting collection contains all the negative numbers plus number zero.
Now that we know the main methods used to filter elements, let’s take a look at what can we do with collections of nullable types in Kotlin.
filterNull
In the cases where we work with a collection of nullable types, we have methods available to be able to easily exclude null elements from our collections.
We are going to create a list of nullable types and check in our test that the null elements get filtered indeed.
@Test
fun `excludes null elements`() {
val numbers = listOf(0, 1, null, 3, null, 5, 6, null, 8, null, 10)
val filtered = numbers.filterNotNull()
assertThat(filtered).containsExactly(0, 1, 3, 5, 6, 8, 10)
}
In this case, if we check the types of each of the lists that we use, we can see how numbers
is of type List<Int?>
whereas filtered
is of type List<Int>
. The compiler is clever enough to infer the types based on the existence of null values; filterNotNull
will always return non-nullable type collections.
filterNotNullTo
If we look at the implementation of filterNotNull
, we can see that it’s using filterNotNullTo
internally.
public fun <T : Any> Iterable<T?>.filterNotNull(): List<T> {
return filterNotNullTo(ArrayList<T>())
}
Like we saw earlier for filterTo
method, this method is the equivalent to that but excluding null values. Therefore our test would look like this using filterNotNullTo
:
@Test
fun `includes non-null elements into an existing list`() {
val existing = mutableListOf(-1, 0, -2, -3)
val numbers = listOf(0, 1, null, 3, null, 5, 6, null, 8, null, 10)
val filtered = numbers.filterNotNullTo(existing)
assertThat(filtered).containsExactly(-1, 0, -2, -3, 0, 1, 3, 5, 6, 8, 10)
}
As you can see it works very similarly to our previous example.
What if we need to filter a collection based on the position or number of elements? You can check how to do that in our article “Getting First/Last N Elements in Kotlin“.
If you are interested in knowing about Kotlin in more detail, we recommend the following books for your own reading:
Conclusion
We’ve seen how to filter elements from a collection in different ways, this is something that you’ll use very frequently in your day to day work if you work with collections. We’ve covered filter
, filterTo
, filterNot
, filterNotTo
, filterNull
and filterNullTo
.
In these series of Kotlin articles we’ll be seeing how powerful Kotin is and how easy it makes our day to day work.
This is all from us today! We hope you’ve enjoyed this article and looking forward to seeing you again soon!
3 comments
You must log in to post a comment.