lznt
lznt

Reputation: 2576

scala map - what's the best way to try a different key if a key doesn't exist

Given a Map in Scala, I want to try a first key, if not found, try a different key, if not found again, return None. The following works as expected:

val scores: Map[String, Int] = Map("Alice" -> 10, "Bob" -> 3)
val singleGet: Option[Int] = scores.get("NotAKey")
println(singleGet) // None
val doubleGet = scores.getOrElse("NotAKey", scores.get("NotAKeyAgain")) // works ok if no type
println(doubleGet) // None

But if I put a type for doubleGet, it errors:

val doubleGet: Option[Int] = scores.getOrElse("NotAKey", scores.get("NotAKeyAgain")) // ERROR

"Expression of type Any doesn't conform to expected type Option[Int]"

So what would be the best way to do this?

Upvotes: 0

Views: 871

Answers (1)

You have a good intuition on what you want to do, which is:
"test one key, if that does not exists, then check the second one".

Also, you are correct on the return type, because you have not provided any default, and there isn't a guarantee of that either key should exists.
But, your problem is that scores.getOrElse("NotAKey", scores.get("NotAKeyAgain")) will return Any.
Why? Because getOrElse returns the LUB (least upper bound) of both alternatives. In this case default is an Option[Int] but the type of a successful retrieval of the first key, is Int.

What you really need is a way to compose two Options, where the second one is used in the case the first one does not exists. And that is exactly what orElse does.

You can create some extension methods over a Map to make the usage more easy.

implicit class MapOps[K, V](private val map: Map[K, V]) extends AnyVal {
  def doubleGet(key1: K, key2: K): Option[V] =
    map.get(key1) orElse map.get(key2)

  def doubleGetOrElse[V1 >: V](key1: K, key2: K)(default: => V1): V1 =
    (map.get(key1) orElse map.get(key2)).getOrElse(default)

  def multiGet(keys: K*): Option[V] =
    keys.iterator.map(key => map.get(key)).foldLeft(Option.empty[V])(_ orElse _)

  def multiGetOrElse[V1 >: V](keys: K*)(default: => V1): V1 =
    keys.iterator.map(key => map.get(key)).foldLeft(Option.empty[V])(_ orElse _).getOrElse(default)
}

val scores: Map[String, Int] = Map("Alice" -> 10, "Bob" -> 3)
scores.doubleGet("A", "B") // res: Option[Int] = None
scores.multiGet("A", "Alice", "B") // res: Option[Int] = Some(10)

Upvotes: 2

Related Questions