I'm new to scala and trying to return the first item from a Seq
that satisfies a condition. Tried to use find
but my condition returns a Future[Boolean]
Example:
val sequence: Seq[Val] = Seq[val1, val2, val3]
def conditions(val: Val): Future[Boolean] = {...}
sequence.find(val => conditions(val))
This does not work if conditions
returns a Future value, is there an equivalent of find for Futures?
CodePudding user response:
No, there is no such equivalent in the standard library. But one could implement that if needed, e.g. like this (note that this could be written a bit shorter, I just thought it might be more readable like this for people new to scala):
def findFuture[T](
sequence: Seq[T]
)(
predicate: T => Future[Boolean]
)(
// ExecutionContext is needed for conditionFuture.flatMap below
implicit ec: ExecutionContext
): Future[Option[T]] = {
if (sequence.isEmpty) {
// no item fulfilled the condition
Future.successful(None)
} else {
// check the first item in the Seq
val candidate: T = sequence.head
val conditionFuture: Future[Boolean] = predicate(candidate)
conditionFuture.flatMap { condition =>
if (condition) {
// return the candidate (wrapped into a Future to suit the
// signature of conditionFuture.flatMap)
Future.successful(Some(candidate))
} else {
// candidate does not match, recursively continue the search
findFuture(sequence.tail)(predicate)
}
}
}
}
Alternatively: maybe it is not needed to (re-)evaluate the asynchronous part of your conditions
for each item?
If you could refactor your code in a way that the Future only needs to be evaluated once, you could maybe end up with this:
val sequence: Seq[Val] = Seq[val1, val2, val3]
def conditions: Future[Val => Boolean] = {...}
conditions.map(c => sequence.find(val => c(val)))
CodePudding user response:
You could use foldLeft
on the sequence like this:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._
val s: Seq[String] = Seq("val1", "val2", "val3")
def conditions(value: String): Future[Boolean] = {
println(s"Check $value")
if (value == "val2") Future.successful(true) else Future.successful(false)
}
val zero: Future[Option[String]] = Future.successful(None)
val f = s.foldLeft(zero) { (acc: Future[Option[String]], value: String) =>
acc.flatMap {
case Some(alreadyFound) => acc
case None => conditions(value).map(pass => if (pass) Some(value) else None)
}
}
Await.result(f, 10.seconds) // Some(val2)
// Prints:
// Check val1
// Check val2
Note that this won't execute conditions
once first value matching is found but it will still iterate over the whole sequence.