
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
In this tutorial, we’ll explore two ways to solve a simple problem – given a sequential collection, how do we find the first element that satisfies a given condition?
We’ll see two ways to solve the problem to understand how different criteria can lead to different solutions.
Our first approach is very practical and uses the resources that the powerful collections library from Scala gives us out-of-the-box. It doesn’t teach us much about algorithms but helps us get the job done effectively.
When we explore the Scala documentation for collections, we’ll find a method that does exactly what we need with the following signature:
def find(p: (A) => Boolean): Option[A]
The search might fail to find a result that satisfies the given predicate. Recognizing that possibility, the method returns an Option[A] instead of a simple A.
Here’s how we can use it to find an existing element:
// say that we have a list of integers:
val integers = 2 :: 4 :: 6 :: 7 :: 8 :: Nil
// and we are interested in the first odd integer:
val predicate = (n: Int) => n % 2 == 1
// let's find it!
val maybeOddInt = integers.find(predicate)
// did we get it?
assertResult(Some(7))(maybeOddInt)
Our second approach uses a tail recursive function, and an implicit value class:
object Finder {
implicit class Wrapper[T](val sequence: Seq[T]) extends AnyVal {
@tailrec
final def findMatch(condition: T => Boolean): Option[T] = {
if (sequence.isEmpty) None
else if (condition(sequence.head)) Some(sequence.head)
else sequence.tail.findMatch(condition)
}
}
}
Here are some elements of this snippet that are worth noticing:
We can test the functionality of our findMatch() method, using the powerful ScalaTest framework:
class FinderSpec extends AnyWordSpec {
import Finder._
"A finder" should {
"find nothing in an empty collection" in {
val collection = Vector[Int]()
val expected = None
val actual = collection.findMatch(_ > 0)
assertResult(expected)(actual)
}
"find nothing if the element is not there" in {
val collection = 2 :: 4 :: 6 :: 8 :: Nil
val expected = None
val actual = collection.findMatch(_ > 8)
assertResult(expected)(actual)
}
"find the first element that matches" in {
val collection =
('a' -> 0) :: ('b' -> 1) :: ('c' -> 2) :: ('d' -> 3) :: Nil
val expected = 'b'
val actual = collection.findMatch(pair => pair._2 > 0).get._1
assertResult(expected)(actual)
}
}
}
In this article, we learned two ways to find the first element of a sequential collection that matches a specific predicate. In general, we should prefer using the collections library because the code is very performant and thoroughly tested.
Nonetheless, by providing our own implementation, we can learn or refresh interesting, idiomatic, and efficient patterns in our coding. Also, it can be a stepping stone to solving variations of the problem, like finding the nth element that satisfies the condition.