Home > Blockchain >  Return the first item from a Sequence that satisfies a given condtion
Return the first item from a Sequence that satisfies a given condtion

Time:08-14

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.

  • Related