How To Filter Collections In Kotlin » The Bored Dev

How To Filter Collections in Kotlin

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“.

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!