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

Chr*_*imm 29 scala future akka

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

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

cmb*_*ter 27

看看这是否适合您:

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)
Run Code Online (Sandbox Code Playgroud)

我们的想法是将地图映射到地图的Iterable一个Tuple关键字以及与该关键字相关的未来结果.从那里,你可以sequenceIterable,然后,一旦你有聚集Future,它映射和转换是IterableTuples通过一个地图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)
}
Run Code Online (Sandbox Code Playgroud)

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

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3})    
val fut = sequenceMap(map)
fut onComplete{
  case Success(m) => println(m)
  case Failure(ex) => ex.printStackTrace()
}
Run Code Online (Sandbox Code Playgroud)

这可能比第一个示例稍微有效,因为它创建的中间集合较少,而且点击次数较少ExecutionContext.

  • 本着同样的精神:`Future.sequence(地图图{case(a,b)=> b map(a - > _)})map(_.toMap)` (4认同)
  • @MarkusMikkolainen,你可以通过将代码扩展到多行而不是我提交的一个内容来使其更具可读性.就效率而言,你将在'ExecutionContext`(+1来自最后的_.toMap)中产生n + 1个额外的命中(除了上面和超出`sequence`之外的更多).不应该是一个太大的交易,但我会尝试发布一个更多镜像`sequence`函数的解决方案,而不是直接使用它来获得更高效率的命令`ExecutionContext`. (2认同)

Jof*_*fer 12

我认为Scala核心最简洁的是

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

Future.traverse(map) { case (k, fv) => fv.map(k -> _) } map(_.toMap)
Run Code Online (Sandbox Code Playgroud)


Tra*_*own 9

更新:你可以.sequenceScalaz 7中实际获得漂亮的语法,而不用太大惊小怪:

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))
Run Code Online (Sandbox Code Playgroud)

然后:

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

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


正如上面评论中的copumpkin注释,Scalaz包含一个带有实例的Traverse类型类,Map[A, _]这是其中一个拼图.另一个是Applicative实例Future,它不在Scalaz 7中(它仍然是针对Future2.9 之前的交叉构建),但是在scalaz-contrib.

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
}
Run Code Online (Sandbox Code Playgroud)

要么:

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

要么:

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] =
  TraverseOpsUnapply(m).sequence
Run Code Online (Sandbox Code Playgroud)

在一个完美的世界中,你可以编写m.sequence,但TraverseOps应该使这种语法成为可能的机制目前无法告诉如何从特定Map实例转到适当的Traverse实例.