Albert
Albert

Reputation: 2115

construct Map with option values

I'd like to build a Map like this:

  def one = "one"
  def two = "two"
  def three = Some("three")

  Map[String, String]("one" -> one, "two" -> two, "three" -> three)

This won't compile because the method three returns an Option instead of a String. I can make this work like this:

Map[String, String]("one" -> one, "two" -> two) ++ three.map(t => Map("three" -> t)).getOrElse(Map.empty[String, String])

Now it will only add the Option to the list when it's Some.

But there must be a more elegant way. (lift-json for example knows how to filter out Options when constructing JValue's).

Any suggestions? (P.S. I've simplified the problem here)

Upvotes: 3

Views: 313

Answers (4)

Malte Schwerhoff
Malte Schwerhoff

Reputation: 12852

In order to provide a nice interface to your clients, you could extend one of the Map's to perform the unpacking:

class MyMap[A, B](private val tuples: (A, Option[B])*)
    extends collection.DefaultMap[A, B] {

  private val container =
    new collection.mutable.HashMap[A, B]()

  container ++= tuples collect {case (k, Some(v)) => (k, v)}

  def iterator = container.iterator
  def get(id: A) = container.get(id)
  override def size = container.size
}

Combine this with an implicit that turns pairs (A, B) into pairs (A, Option[B]):

implicit def pairToPairWithSomeValue[A, B](t: (A, B)): (A, Option[B]) =
  (t._1, Some(t._2))

And use it as:

def one = "one"
def two = "two"
def three = Some("three")
def four = None


val mm = new MyMap("one" -> one, "two" -> two, "three" -> three,
                   "four" -> four)

mm foreach println
  /* (two,two), (one,one), (three,three) */

Upvotes: 0

Rex Kerr
Rex Kerr

Reputation: 167931

You have two kinds of map, e.g.:

val map1 = Map("one" -> 1, "two" -> 2)
val map2 = Map("three" -> Some(3), "four" -> None)

You can unpack the latter:

map2.collect { case (k,Some(v)) => (k,v) }

and merge the maps:

map1 ++ map2.collect{ case (k,Some(v)) => (k,v) }

Upvotes: 4

Sven Viehmeier
Sven Viehmeier

Reputation: 418

If you know which values are Options and which are not, you can simply call getOrElse directly after the method call:

Map[String, String]("one" -> one, "two" -> two, "three" -> three.getOrElse("empty"))

If you don't know which methods will return an Option, you could use an implicit conversion to extract the value from the Option or set it to an default value if it is None:

implicit def optToStr(a : Option[String]) : String = a.getOrElse("empty")
Map[String, String]("one" -> one, "two" -> two, "three" -> three)

You could also delete the default key-value pairs afterwards by using a filter on the map although this is not very elegant (perhaps somebody else knows a better solution in this case).

Upvotes: 2

missingfaktor
missingfaktor

Reputation: 92126

Map("one" -> one, "two" -> two) ++ three.map("three" -> _) will work too.

Upvotes: 7

Related Questions