AkkaStreamsを使用して複数の大きなファイルを読み取って各行を処理したいと思います。各キーが(identifier -> value)
で構成されていると想像してください。新しい識別子が見つかった場合は、その識別子とその値をデータベースに保存します。それ以外の場合、行のストリームの処理中に識別子がすでに見つかっている場合は、値のみを保存します。そのためには、Map
で既に見つかった識別子を保持するために、ある種の再帰的なステートフルフローが必要だと思います。このフローでは、(newLine, contextWithIdentifiers)
のペアを受け取ると思います。
AkkaStreamsを調べ始めたところです。ステートレス処理を自分で行うことができると思いますが、contextWithIdentifiers
を維持する方法についてはわかりません。正しい方向へのポインタをいただければ幸いです。
たぶんstatefulMapConcat
のようなものがあなたを助けることができます:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Sink, Source}
import scala.util.Random._
import scala.math.abs
import scala.concurrent.ExecutionContext.Implicits.global
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
//encapsulating your input
case class IdentValue(id: Int, value: String)
//some random generated input
val identValues = List.fill(20)(IdentValue(abs(nextInt()) % 5, "valueHere"))
val stateFlow = Flow[IdentValue].statefulMapConcat{ () =>
//state with already processed ids
var ids = Set.empty[Int]
identValue => if (ids.contains(identValue.id)) {
//save value to DB
println(identValue.value)
List(identValue)
} else {
//save both to database
println(identValue)
ids = ids + identValue.id
List(identValue)
}
}
Source(identValues)
.via(stateFlow)
.runWith(Sink.seq)
.onSuccess { case identValue => println(identValue) }
数年後、(1対Nではなく)1対1のマッピングのみが必要な場合に私が作成した実装を次に示します。
import akka.stream.stage.{GraphStage, GraphStageLogic}
import akka.stream.{Attributes, FlowShape, Inlet, Outlet}
object StatefulMap {
def apply[T, O](converter: => T => O) = new StatefulMap[T, O](converter)
}
class StatefulMap[T, O](converter: => T => O) extends GraphStage[FlowShape[T, O]] {
val in = Inlet[T]("StatefulMap.in")
val out = Outlet[O]("StatefulMap.out")
val shape = FlowShape.of(in, out)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) {
val f = converter
setHandler(in, () => Push(out, f(grab(in))))
setHandler(out, () => pull(in))
}
}
テスト(およびデモ):
behavior of "StatefulMap"
class Counter extends (Any => Int) {
var count = 0
override def apply(x: Any): Int = {
count += 1
count
}
}
it should "not share state among substreams" in {
val result = await {
Source(0 until 10)
.groupBy(2, _ % 2)
.via(StatefulMap(new Counter()))
.fold(Seq.empty[Int])(_ :+ _)
.mergeSubstreams
.runWith(Sink.seq)
}
result.foreach(_ should be(1 to 5))
}