6

Let's say I have a Scala list List("apple", "orange", "banana", "chinese gooseberry")*. I want to search this list and return either the previous item in the list in relation to an item I already have.

For example: getPrevious(fruit: String, fruits: List[String]): Option[String] should return

  • Some("apple") if I call it with a fruit arg of "orange";
  • Some("banana") for "chinese gooseberry";
  • None if I call it with "apple" (no previous element exists) or "potato" (not present in the list).

Easily done imperatively, but how can I do this in an elegant functional manner? The best I can come up with is the following:

def previous(fruit: String, fruits: List[String]): Option[String] =
  fruits.sliding(2)
  .filter { case List(previous, current) => current == fruit }
  .toList
  .headOption
  .map { case List(previous, current) => previous }

It works, but it isn't elegant or efficient. I particularly hate converting the filter iterator toList. How can I improve it?

(*as an aside, is List the best collection to use for a sliding iteration?)

yǝsʞǝla
  • 16,272
  • 2
  • 44
  • 65
sam-w
  • 7,478
  • 1
  • 47
  • 77

3 Answers3

14

Here's a shorter version using collectFirst:

def previous(fruit: String, fruits: List[String]): Option[String] =
    fruits.sliding(2).collectFirst{ case List(previous, `fruit`) => previous}

Note the backticks surrounding fruit to match the parameter value. Using collectFirst will also stop at the first match, rather than running through the entire iterator with filter.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
8

I think this is a case where just straight up recursion and pattern matching is both efficient and easier to read:

@annotation.tailrec
def getPrevious(fruit: String, fruits: List[String]): Option[String] = fruits match  {
  case Nil               => None
  case x :: `fruit` :: _ => Some(x)
  case _ :: xs           => getPrevious(fruit, xs)
}
stew
  • 11,276
  • 36
  • 49
1

The first simple solution that comes to mind is:

import scala.util.Try
def previous(fruit: String, fruits: List[String]) = Try(fruits(fruits.indexOf(fruit) - 1)).toOption

There should certainly be more efficient ones.

Kigyo
  • 5,668
  • 1
  • 20
  • 24
  • 1
    This looks clever but cost of throwing exceptions is high + exceptions should be used in exceptional cases, they are not values which goes against FP principles. Nonetheless I like the idea ;) – yǝsʞǝla Jul 31 '14 at 01:59
  • Yeah, I know, it also iterates the whole `List` twice in the worst case. Indexing is sadly `O(n)` on `List`. It was just a crazy idea. ;) – Kigyo Jul 31 '14 at 10:13