如何将Map [A​​,Future [B]]转换为Future [Map [A​​,B]]?

时间:2013-07-04 23:16:22

标签: scala akka future

我一直在使用Scala Akka库并遇到了一些问题。正如标题所说,我需要将Map[A, Future[B]]转换为Future[Map[A,B]]。我知道可以将Future.sequence用于像列表这样的Iterables,但在这种情况下不起作用。

我想知道:在Scala中有一种干净的方式来进行转换吗?

7 个答案:

答案 0 :(得分:26)

看看这是否适合你:

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3})    
val fut = Future.sequence(map.map(entry => entry._2.map(i => (entry._1, i)))).map(_.toMap)

我们的想法是将地图映射到地图密钥Iterable的{​​{1}},并将未来的结果与该密钥相关联。在那里,您可以Tuple sequence,然后在汇总Iterable之后,将其映射并通过{{将Future Iterable转换为地图1}}。

现在,这种方法的替代方法是尝试做类似于Tuples函数正在做的事情,并进行一些调整。您可以像这样编写toMap函数:

sequence

然后在这样的例子中使用它:

sequenceMap

这可能比第一个示例稍微有效,因为它创建的中间集合较少,对def sequenceMap[A, B](in: Map[B, Future[A]])(implicit executor: ExecutionContext): Future[Map[B, A]] = { val mb = new MapBuilder[B,A, Map[B,A]](Map()) in.foldLeft(Promise.successful(mb).future) { (fr, fa) => for (r <- fr; a <- fa._2.asInstanceOf[Future[A]]) yield (r += ((fa._1, a))) } map (_.result) } 的点击次数较少。

答案 1 :(得分:10)

我认为核心Scala最简洁的是

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3}) 

Future.traverse(map) { case (k, fv) => fv.map(k -> _) } map(_.toMap)

答案 2 :(得分:8)

更新:您实际上可以在Scalaz 7中获得漂亮的.sequence语法,而不会有太多的麻烦:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ Future, future }

import scalaz._, Scalaz.{ ToTraverseOps => _, _ }
import scalaz.contrib.std._

val m = Map("a" -> future(1), "b" -> future(2), "c" -> future(3))

然后:

scala> m.sequence.onSuccess { case result => println(result) }
Map(a -> 1, b -> 2, c -> 3)

原则上,没有必要像这样隐藏ToTraverseOps,但是现在它可以解决问题。有关Traverse类型类,依赖项等的更多详细信息,请参阅下面的其余答案。


正如上面评论中的copumpkin注释一样,Scalaz包含一个Traverse type class,其中包含Map[A, _]的实例,这是此处的拼图之一。另一部分是Applicative的{​​{1}}实例,它不在Scalaz 7中(它仍与Future 2.9之间交叉构建),但在{{3}中}。

Future

或者:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz._, Scalaz._
import scalaz.contrib.std._

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] = {
   type M[X] = Map[A, X]
   (m: M[Future[B]]).sequence
}

或者:

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] =
  Traverse[({ type L[X] = Map[A, X] })#L] sequence m

在一个完美的世界中,你可以写def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] = TraverseOpsUnapply(m).sequence ,但应该使这种语法成为可能的m.sequence机制目前无法告诉如何从特定的{{1}实例到适当的TraverseOps实例。

答案 3 :(得分:4)

这也有效,其中的想法是使用序列结果(地图的值)来触发一个声明,表明你可以开始从地图中检索值。 mapValues为您提供了a non-strict view of your map,因此value.get.get仅在您检索值时应用。没错,你可以保留你的地图! the puzzlers in that link的免费广告。

import concurrent._
import concurrent.duration._
import scala.util._
import ExecutionContext.Implicits.global

object Test extends App {
  def calc(i: Int) = { Thread sleep i * 1000L ; i }
  val m = Map("a" -> future{calc(1)}, "b" -> future{calc(2)}, "c" -> future{calc(3)})
  val m2 = m mapValues (_.value.get.get)
  val k = Future sequence m.values
  val p = Promise[Map[String,Int]]
  k onFailure { case t: Throwable => p failure t }
  k onSuccess { case _ => p success m2 }
  val res = Await.result(p.future, Duration.Inf) 
  Console println res
}

这里是REPL,你看到它通过打印所有值来强制m2地图:

scala> val m2 = m mapValues (_.value.get.get)
m2: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3)

这显示了未来仍然存在的未来:

scala>   val m2 = m mapValues (_.value.get.get)
java.util.NoSuchElementException: None.get

答案 4 :(得分:1)

只需创建一个等待地图值中所有未来的新未来,然后构建要返回的地图。

答案 5 :(得分:1)

我会尽量避免使用过度设计的基于Scalaz的超级功能解决方案(除非您的项目已经基于Scalaz,并且拥有大量的&#34;计算复杂的代码;并且#34; overengineered&#34; 34;评论):

// the map you have
val foo: Map[A, Future[B]] = ???

// get a Seq[Future[...]] so that we can run Future.sequence on it
val bar: Seq[Future[(A, B)]] = foo.map { case (k, v) => v.map(k -> _) }

// here you go; convert back `toMap` once it completes
Future.sequence(bar).onComplete { data =>
    // do something with data.toMap
}

但是,应该可以安全地假设您的地图值是以某种方式从地图键生成的,这些地图键最初位于Seq,例如List,并且构建初始Map的代码部分由您控制,而不是从其他地方发送。因此,我个人会采取更简单/更清洁的方法,而不是首先使用Map[A, Future[B]]开始。

def fetchAgeFromDb(name: String): Future[Int] = ???

// no foo needed anymore

// no Map at all before the future completes
val bar = personNames.map { name => fetchAgeFromDb(name).map(name -> _) }

// just as above
Future.sequence(bar).onComplete { data =>
    // do something with data.toMap
}

答案 6 :(得分:0)

这个解决方案是否可以接受: 没有执行上下文,这应该有效...

def removeMapFuture[A, B](in: Future[Map[A, Future[B]]]) = {
  in.flatMap { k =>
    Future.sequence(k.map(l =>
      l._2.map(l._1 -> _)
    )).map {
      p => p.toMap
    }
  }
}