我希望将列表项转换为单个地图,作为我的Akka Streams工作流中的一个阶段。举个例子,假设我有以下课程。
case class MyClass(myString: String, myInt: Int)
我想将List
MyClass
个实例转换为按Map
键myString
的{{1}}个实例。
因此,如果我有List(MyClass("hello", 1), MyClass("world", 2), MyClass("hello", 3))
,我希望hello
地图映射到List(1, 3)
,world
映射到List(2)
。
以下是我到目前为止的情况。
val flowIWant = {
Flow[MyClass].map { entry =>
entry.myString -> entry.myInt
} ??? // How to combine tuples into a single map?
}
此外,流程最终会生成单个地图实体是理想的,因此我可以为下一个阶段单独处理每个地图(我想对每个地图实体个人进行操作)。
我不确定这是fold
类型的操作还是什么。谢谢你的帮助。
答案 0 :(得分:0)
你真正想要得到的并不是很清楚。从您陈述问题的方式来看,我至少看到了您可能想要的以下转换:
Flow[List[MyClass], Map[String, Int], _]
Flow[List[MyClass], Map[String, List[Int]], _]
Flow[MyClass, (String, Int), _]
Flow[MyClass, (String, List[Int]), _]
从你的措辞中我怀疑你很可能想要像最后一个这样的东西,但是进行这样的转换并不是真的有意义,因为它无法发出任何东西 - 按顺序排列组合您需要的所有值来读取整个输入。
如果您有一个MyClass
的传入流并希望从中获取Map[String, List[Int]]
,则除了将其附加到折叠接收器并执行该流直到完成之外别无选择。例如:
val source: Source[MyClass, _] = ??? // your source of MyClass instances
val collect: Sink[MyClass, Future[Map[String, List[Int]]] =
Sink.fold[Map[String, List[Int]], MyClass](Map.empty.withDefaultValue(List.empty)) {
(m, v) => m + (v.myString -> (v.myInt :: m(v.myString)))
}
val result: Future[Map[String, List[Int]]] = source.toMat(collect)(Keep.right).run()
答案 1 :(得分:0)
我想你想scan
:
source.scan((Map.empty[String, Int], None: Option((String, Int))))((acc, next) => { val (map, _)
val newMap = map.updated(next._1 -> map.getOrElse(next._1, List()))
(newMap, Some(newMap.get(next._1)))}).map(_._2.get)
通过这种方式,您可以检查Map
的内容,直到内存耗尽。 (与最后一个元素相关的内容位于包含在Option
中的初始元组的值部分。)
答案 2 :(得分:0)
这可能是您正在寻找的:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Sink, Source}
import scala.util.{Failure, Success}
object Stack {
def main(args: Array[String]): Unit = {
case class MyClass(myString: String, myInt: Int)
implicit val actorSystem = ActorSystem("app")
implicit val actorMaterializer = ActorMaterializer()
import scala.concurrent.ExecutionContext.Implicits.global
val list = List(MyClass("hello", 1), MyClass("world", 2), MyClass("hello", 3))
val eventualMap = Source(list).fold(Map[String, List[Int]]())((m, e) => {
val newValue = e.myInt :: m.get(e.myString).getOrElse(Nil)
m + (e.myString -> newValue)
}).runWith(Sink.head)
eventualMap.onComplete{
case Success(m) => {
println(m)
actorSystem.terminate()
}
case Failure(e) => {
e.printStackTrace()
actorSystem.terminate()
}
}
}
}
使用此代码,您将获得以下输出:
Map(hello -> List(3, 1), world -> List(2))
如果您想要输出以下内容:
Vector(Map(), Map(hello -> List(1)), Map(hello -> List(1), world -> List(2)), Map(hello -> List(3, 1), world -> List(2)))
只需使用扫描代替折叠并使用Sink.seq运行。
折叠和扫描之间的区别在于,在按下之前折叠等待上游完成,而扫描会将每次更新推送到下游。