Create map based on condition from Future of List

2019-08-17 06:52发布

问题:

I have method with param type Future[List[MyRes]]. MyRes has two option fields id and name. Now I want to create map of id and name if both present. I am able to create map with default value as follow but I don't want to have default value just skip the entry with null value on either.

def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
  myRes.map (
    _.map(
      o =>
      (o.id match {
        case Some(id) => id.toLong
        case _ => 0L 
      }) ->
      (o.name match {
        case Some(name) => name
        case _ => ""
      })
  ).toMap)

Any suggestion?

回答1:

You are looking for collect :)

myRes.map {  
   _.iterator
    .map { r => r.id -> r.name }
    .collect { case(Some(id), Some(name) => id -> name }
    .toMap
}

If your MyRes thingy is a case class, then you don't need the first .map:

myRes.map { 
  _.collect { case MyRes(Some(id), Some(name)) => id -> name }
  .toMap
}

collect is like .map, but it takes a PartialFunction, and skips over elements on which it is not defined. It is kinda like your match statement but without the defaults.

Update: If I am reading your comment correctly, and you want to log a message when either field is a None, collect won't help with that, but you can do flatMap:

 myRes.map {
   _.flatMap {
     case MyRes(Some(id), Some(name)) => Some(id -> name)
     case x => loger.warn(s"Missing fields in $x."); None
   }
   .toMap
 }


回答2:

Try this:

def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
  myRes.map (
    _.flatMap(o =>
      (for (id <- o.id; name <- o.name) yield (id.toLong -> name)).toList
    ).toMap
  )
}

The trick is flattening List[Option[(Long,String)]] by using flatMap and converting the Option to a List.