![](/img/trans.png)
[英]Scala: Is ForComprehension a Functional approach or imperative?
[英]Scala transform iterative approach to a functional approach for Iterator
我有以下函数处理一系列搜索事件,这些事件需要在搜索流中组合在一起,以防它们相关。
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 世界)。
任何人都可以向我提供一些有关如何以功能方式实现相同结果的提示。
这是那种你想使用尾递归的东西:
@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
编写相同的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 次,并取所有执行的平均时间。
最初的实现每次运行大约需要 42 毫秒。 递归算法大约需要 28ms FoldLeft 大约需要 29ms
简单地对事件数组进行排序并将其转换为迭代器大约需要 20 毫秒。
我希望这可以解决程序方法是否总是会产生比函数更好的性能的争论。 有一个办法提出具体修改和权衡,但简单地用一个循环替代递归或切换到使用可变容器加快这实现起来不是优化。
据我所知,收藏库中没有简单的内置解决方案。 正如@Dima 所说,您应该为此使用递归。
请注意,如果您非常关心性能,那么使用var
和mutable
集合的初始解决方案可能是最快的。 只要您有充分的理由并且只要突变保持在特定方法的本地,可变性就很好。
为了让我自己非常清楚,我不鼓励您对其进行微优化,除非您有一个基准测试表明这以不可忽略的方式帮助您的应用程序的性能。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.