Scala: How to get the first match in a sequence and immediately return

As a quick note to self, I wrote this Scala code as a way to (a) find the first element in a sequence, and then (b) return that element without traversing the rest of the sequence.

I initially thought about writing this code with a while loop, for loop, or for expression — because I knew I needed a loop and a way to break out of a loop — but then I realized that an iterator would help me out here.

Also, please note that there is a potentially-better solution after this — the one that uses the “view.”

A first solution

So without any further ado, here’s this solution:

/*
 * Benefits of the iterator/map/find/flatten approach:
 * 1. It stops processing as soon as it finds a match (efficient).
 * 2. If it finds a match, it returns that match, wrapped in another Some.
 * 3. If it doesn't find any match, it returns None.
*/
def loopThruSmallImagesToFindOneThatWorks(
    lgImage: BufferedImage,
    lgImageRows: Int,
    lgImageCols: Int,
    fiveYearIconFilenames: Seq[String]    
): Option[Point] =
    // create a lazy iterator to process files one at a time.
    fiveYearIconFilenames.iterator
        .map(filename => tryToFindSmallImageInScreenshot(lgImage, lgImageRows, lgImageCols, filename))
        .find(opt => opt.isDefined)  // find the first Option that is defined; return it in a Some
        .flatten                     // then you need to flatten that outside Some

Functional programming with iterators

This type of programming is known as “functional programming with iterators.” Other names for it are “stream processing” and “lazy evaluation.” The way the code exits when the first element is found is known as “short-circuiting” or “early termination.”

The iterator approach employs lazy evaluation, which means that it only processes one element at a time, as needed. This can be very efficient when working with large collections, because you don’t have to load the entire collection into memory, but in this case the reason I use it is because I want to exit this code as soon as the first match is found.

Another way to write this code is like this, using map and collectFirst:

def loopThruSmallImagesToFindOneThatWorks(
    lgImage: BufferedImage,
    lgImageRows: Int,
    lgImageCols: Int,
    fiveYearIconFilenames: Seq[String]
): Option[Point] =
    fiveYearIconFilenames.iterator
        .map(filename => tryToFindSmallImageInScreenshot(lgImage, lgImageRows, lgImageCols, filename))
        .collectFirst { case Some(point) => point }

The potential benefits are:

  1. collectFirst: This method is a bit more idiomatic and concise. It “collects” the first element that matches the specified pattern, which in this case is the first Some(point).
  2. Elimination of flatten: Using collectFirst eliminates the need to call flatten since it directly extracts the Point from the Some.

Another potential solution: view, flatMap, and headOption

FWIW, I just threw that function into a couple of A.I. tools, and a new suggestion I got for the function code looks like this:

def loopThruSmallImagesToFindOneThatWorks(
    lgImage: BufferedImage,
    lgImageRows: Int,
    lgImageCols: Int,
    fiveYearIconFilenames: Seq[String]    
): Option[Point] =
  fiveYearIconFilenames
    .view  // Creates a non-strict view of the sequence
    .flatMap(filename => tryToFindSmallImageInScreenshot(lgImage, lgImageRows, lgImageCols, filename))
    .headOption  // Returns the first Some, or None if all are None

Potential benefits of this approach are:

  1. It uses view instead of iterator. This creates a non-strict view of the sequence, which is lazy like an iterator, but can be more flexible.
  2. It uses flatMap instead of map followed by flatten. This combines the mapping and flattening operations into one step. (Experienced Scala programmers know this, so this might be an approach they’d use.)
  3. It uses headOption instead of find(opt => opt.isDefined).flatten. This gives you the first Some value, or None if all results are None.

Again, I just want to show another option. As a programmer, I think it’s good to see and know the available options, and then you can decide which solution you like best.