Scala将迭代方法转换为Iterator的功能方法

时间:2019-04-30 10:52:21

标签: scala functional-programming

我具有以下功能,用于处理一系列搜索事件,如果它们相关,则需要在搜索流中将它们分组在一起。

  def split(eventsIterator: Iterator[SearchFlowSearchEvent]): Iterator[SearchFlow] = {

    val sortedEventsIterator = eventsIterator.toList.sortBy(_.evTimeMillis).iterator


    val searchFlowsEvents: mutable.MutableList[mutable.MutableList[SearchFlowSearchEvent]] = mutable.MutableList()
    var currentSearchFlowEvents: mutable.MutableList[SearchFlowSearchEvent] = mutable.MutableList()
    var previousEvent: SearchFlowSearchEvent = null
    while (sortedEventsIterator.hasNext) {
      val currentEvent = sortedEventsIterator.next()

      if (isSameFlow(previousEvent, currentEvent)) {
        currentSearchFlowEvents += currentEvent
      } else {
        currentSearchFlowEvents = mutable.MutableList()
        currentSearchFlowEvents += currentEvent
        searchFlowsEvents += currentSearchFlowEvents
      }

      previousEvent = currentEvent
    }


    searchFlowsEvents
      .map(searchFlowEvents => model.SearchFlow(searchFlowEvents.toList))
      .iterator
  }

执行上述事件分组的方法是迭代的(我来自Java世界)。

任何人都可以为我提供一些有关如何以功能方式实现相同结果的提示。

2 个答案:

答案 0 :(得分:3)

这种事情,您想将尾递归用于:

        @tailrec 
        def groupEvents(
          in: Iterator[SearchFlowSearchEvent],
          out: List[List[SearchFlowSearchEvent]] = Nil
        ): List[List[SearchFlowSearchEvent]] = if (in.hasNext) {
          val next = in.next
          out match {
            case Nil => groupEvents(in, List(List(next)))
            case (head :: tail) :: rest if isSameFlow(head, next) => groupEvents(in, (next :: head :: tail) :: rest)
            case rest => groupEvents(in, List(next) :: rest)
          }
       } else out.map(_.reverse).reverse 

out包含到目前为止收集的组(以相反的顺序-参见下文)。 如果为空,则开始一个新的。否则,请查看第一个元素(最后一个组),然后检查那里的第一个元素(最后一个事件)。如果流程相同,则将当前事件添加到该组,否则添加一个新组。重复。

最后(如果迭代器为空),反转列表,然后创建流。

在scala中,常见的是在这种情况下以相反的顺序组装列表。这是因为附加到链表的末尾(或查看最后一个元素)需要线性时间,这会使整个操作变成二次方。取而代之的是,我们总是前置(恒定时间),然后在最后反向(线性)。

或者,您可以使用foldLeft编写相同的内容,但就我个人而言,在这种情况下,我发现一个递归实现更加清晰,尽管更长(在功能上,它们是等效的):

    in.foldLeft[List[List[SearchFlowSearchEvent]]](Nil) {
       case (Nil, next) => List(List(next))
       case ((head :: tail) :: rest, next) if isSameFlow(head, next) => 
          (next :: head :: tail) :: rest
       case (rest, next) => List(next) :: rest
    }.map { l => SearchFlow(l.reverse) }.reverse

更新要解决性能问题,请参阅另一篇文章的评论。我在MacBook Pro,Mac OS 10.13.5、2.9 GHz i7、16G RAM和scala 2.11.11(默认REPL设置)上对这三种解决方案进行了基准测试。

输入的事件为100000个事件,这些事件分为14551个组。 预热后,我将每个实现运行了约500次,并花费了所有执行的平均时间。

原始实现每次运行耗时约42ms。 递归算法约需28ms FoldLeft约为29ms

简单地将事件数组排序并将其转换为迭代器大约需要20毫秒。

我希望这能解决程序性方法是否总是比功能性更好的争论。有一种方法可以通过进行特定的更改和权衡来加快实现速度,但是仅用循环替换递归或切换为使用可变容器并不是一种优化。

答案 1 :(得分:-1)

据我所知,集合库中没有针对此的简单内置解决方案。正如@Dima所说,您应该为此使用递归。

请注意,如果您非常关心性能,那么使用varmutable集合的初始解决方案可能是最快的。只要您有充分的理由,并且只要突变在特定方法中保持局部性,变异性就可以。

为了使自己很清楚,我鼓励您进行微优化,除非您有一个基准表明它以一种不可忽视的方式帮助您提高了应用程序的性能。