在Scala中处理多个并发流的惯用方法

时间:2018-05-28 12:36:53

标签: scala scala-collections akka-stream fs2 monix

我有一个流列表,在调用它们的next()时会随机休眠一段时间,然后从不同的来源读取一个字符。

我正在尝试编写一个将继续调用这些流的消费者,直到EOF并在运行时构建这些流的公共字典。

到目前为止,我使用ConcurrentHashMap作为字典,只是为每个流使用者创建一个新线程。

虽然我的解决方案有效,但它似乎非常简单,我想知道是否有更好的用途,例如monixfs2

1 个答案:

答案 0 :(得分:1)

根据问题的描述和后续评论,我假设存在多个Iterator[Char]来源:

val allSources : Iterable[Iterator[Char]] = ???

问题是:如何从这些迭代器中同时收集String值以形成String到count的映射。

基于流的解决方案

首先,我们需要根据分隔符将每个迭代器转换为字符串值的迭代器:

trait Word {
  val data : String
}

object EmptyWord extends Word {
  override val data = ""
}

case class PartialWord(val data : String) extends Word

case class WholeWord(val data : String) extends Word

val appendToWord : Char => (Word, Char) => Word = 
  (separator) => (originalWord, appendChar) => originalWord match {
    case PartialWord(d) => 
      if(appendChar == separator)
        WholeWord(d)
      else
        PartialWord(d + appendChar)
    case _ => PartialWord(appendChar.toString)
  }

val isWholeWord : Word => Boolean = (_ : Word) match {
  case _ : WholeWord => true
  case _             => false
}

//using space as separator
val convertCharIterator : Iterator[Char] => Iterator[String] = 
  (_ : Iterator[Char])
    .scanLeft(EmptyWord)(appendToWord(' '))
    .filter(isWholeWord)
    .map(_.data)

我们现在可以转换所有迭代器来生成字符串,我们可以将所有迭代器组合成一个迭代器:

val allWordSource : Iterator[String] = 
  allSources.map(convertCharIterator)
            .reduceOption( _ ++ _)
            .getOrElse(Iterator.empty[String])

此Iterator现在可以作为计算您的计数的akka​​流的来源:

val addToCounter : (Map[String, Int], String) => Map[String, Int] = 
  (counter, word) => 
    counter.updated(word, counter.getOrElse(word, 0) + 1)

val counter : Future[Map[String, Int]] = 
  Source
    .fromIterator( () => allWordSource)
    .runFold(Map.empty[String, Int])(addToCounter)