在Akka Stream中将列表转换为地图

时间:2017-08-15 16:52:04

标签: scala akka-stream

我希望将列表项转换为单个地图,作为我的Akka Streams工作流中的一个阶段。举个例子,假设我有以下课程。

case class MyClass(myString: String, myInt: Int)

我想将List MyClass个实例转换为按MapmyString的{​​{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类型的操作还是什么。谢谢你的帮助。

3 个答案:

答案 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运行。

折叠和扫描之间的区别在于,在按下之前折叠等待上游完成,而扫描会将每次更新推送到下游。