Akka流 - 按流中的元素数量进行过滤

时间:2017-08-02 18:26:54

标签: scala stream akka akka-stream

我正在使用Scala编写应用程序,而我正在使用Akka流。

有一次,我需要过滤出N个元素少于N个的流,并给出N.例如,使用N=5

Source(List(1,2,3)).via(myFilter)       // => List()
Source(List(1,2,3,4)).via(myFilter)     // => List()

将成为空流,

Source(List(1,2,3,4,5)).via(myFilter)   // => List(1,2,3,4,5)
Source(List(1,2,3,4,5,6)).via(myFilter) // => List(1,2,3,4,5,6)

将保持不变。

当然,我们无法知道流中的元素数量,直到它结束,等到最后再推出它可能不是最好的主意。

所以,相反,我考虑过以下算法:

  1. 对于前N-1个元素,只需缓冲它们,而不进一步传递;
  2. 如果输入流在到达第N个元素之前完成,则输出空流;
  3. 如果输入流到达第N个元素,则输出缓冲的N-1个元素,然后输出第N个元素,然后传递以下所有元素。
  4. 但是,我不知道如何构建实现它的Flow元素。我可以使用一些内置的Akka元素吗?

    编辑:

    好的,所以昨天我玩了它,我想出了类似的东西:

    Flow[Int].
      prefixAndTail(N).
      flatMapConcat {
        case (prefix, tail) if prefix.length == N =>
          Source(prefix).concat(tail)
        case _ =>
          Source.empty[Int]
      }
    

    它会做我想要的吗?

2 个答案:

答案 0 :(得分:1)

这可能是其中一个小状态"可以走很长的路。即使解决方案不是纯粹的功能",更新状态也将被系统的其余部分隔离并且无法访问。我认为这是scala的优点之一:当FP解决方案不明显时,你总能以孤立的方式恢复到命令式......

完成的Flow将是多个子部分的组合。第一个Flow将您的元素分组为大小为N的序列:

val group : Int => Flow[Int, Seq[Int], _] = 
  (N) => Flow[Int] grouped N

现在对于非功能性部分,只有第一个序列的大小合适才允许分组Seq值的过滤器:

val minSizeRequirement : Int => Seq[Int] => Boolean = 
  (minSize) => {
    var isFirst : Boolean = True

    var passedMinSize : Boolean = False

    (testSeq) => {
      if(isFirst) {
        isFirst = False
        passedMinSize = testSeq.size >= minSize
        passedMinSize
      }
      else
        passedMinSize
      }
    }
  }

val minSizeFilter : Int => Flow[Seq[Int], Seq[Int], _] = 
  (minSize) => Flow[Seq[Int]].filter(minSizeRequirement(minSize))

最后一步是将Seq[Int]值转换回Int值:

val flatten = Flow[Seq[Int]].flatMapConcat(l => Source(l))

最后,将它们组合在一起:

val combinedFlow : Int => Flow[Int, Int, _] =
  (minSize) => 
    group(minSize) 
      .via(minSizeFilter(minSize))
      .via(flatten)

答案 1 :(得分:1)

也许statefulMapConcat可以帮到你:

import akka.actor.ActorSystem
import akka.stream.scaladsl.{Sink, Source}
import akka.stream.{ActorMaterializer, Materializer}

import scala.collection.mutable.ListBuffer
import scala.concurrent.ExecutionContext

object StatefulMapConcatExample extends App {

  implicit val system: ActorSystem = ActorSystem()
  implicit val materializer: Materializer = ActorMaterializer()
  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global

  def filterLessThen(threshold: Int): (Int) => List[Int] = {
    var buffering = true
    val buffer: ListBuffer[Int] = ListBuffer()
    (elem: Int) =>
      if (buffering) {
        buffer += elem
        if (buffer.size < threshold) {
          Nil
        } else {
          buffering = false
          buffer.toList
        }
      } else {
        List(elem)
      }
  }

  //Nil
  Source(List(1, 2, 3)).statefulMapConcat(() => filterLessThen(5))
    .runWith(Sink.seq).map(println)

  //Nil
  Source(List(1, 2, 3, 4)).statefulMapConcat(() => filterLessThen(5))
    .runWith(Sink.seq).map(println)

  //Vector(1,2,3,4,5)
  Source(List(1, 2, 3, 4, 5)).statefulMapConcat(() => filterLessThen(5))
    .runWith(Sink.seq).map(println)

  //Vector(1,2,3,4,5,6)
  Source(List(1, 2, 3, 4, 5, 6)).statefulMapConcat(() => filterLessThen(5))
    .runWith(Sink.seq).map(println)
}