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

piah890a  于 2022-11-29  发布在  其他
关注(0)|答案(7)|浏览(157)

我一直在使用Scala Akka库,但遇到了一个小问题。正如标题所述,我需要将Map[A, Future[B]]转换为Future[Map[A,B]]。我知道可以将Future.sequence用于List之类的Iterables,但在本例中不起作用。
我心里纳闷:Scala中是否有一种简洁方法来进行这种转换?

xmjla07d

xmjla07d1#

看看这是否适合您:

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)

我们的想法是把MapMap到一个Iterable,得到Map的一个Tuple的键和与这个键相关的未来的结果,从那里你可以得到sequence这个Iterable,然后一旦你得到了聚合的Future,Map它,并通过toMapTuplesIterable转换成一个Map。
现在,这种方法的另一种选择是尝试做一些类似于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)
}

然后在下面的例子中使用它:

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()
}

这可能比第一个示例稍微高效一些,因为它创建的中间集合更少,并且对ExecutionContext的命中更少。

k10s72fa

k10s72fa2#

我认为我们可以用核心Scala 2.12.x最简洁的方式

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

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

6qftjkof3#

更新:您实际上可以在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, _]的示例,这是这里的拼图之一。另一个拼图是FutureApplicative示例,它不在Scalaz 7中(它仍然是针对Future 2.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
}

或者:

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,但是应该使这种语法成为可能的TraverseOps机制目前还不能告诉你如何从一个特定的Map示例到适当的Traverse示例。

qnakjoqk

qnakjoqk4#

这也是可行的,其中的想法是使用序列的结果(Map的值)来激发一个承诺,说你可以开始从你的Map检索值。mapValues给你一个非严格的视图你的Map,所以value.get.get只适用于当你检索的值。这是正确的,你可以保留你的Map!免费广告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,您可以看到它通过打印m2Map的所有值来强制m2Map:

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
6yoyoihd

6yoyoihd5#

只需创建一个新的future,等待Map值中的所有future,然后构建一个Map返回。

jm2pwxwz

jm2pwxwz6#

我会尽量避免使用过度设计的基于Scalaz的超功能解决方案(除非你的项目已经大量基于Scalaz并且有大量“计算复杂”的代码;无意冒犯“过度设计”的评论):

// 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
}

但是,可以安全地假设您的Map值是从Map键生成的,Map键最初驻留在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
}
guicsvcw

guicsvcw7#

此解决方案是否可接受:如果没有执行上下文,这应该可以工作...

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
    }
  }
}

相关问题