我有一个序列Seq[T]
,我想做部分归约。例如,对于一个Seq[Int]
,我想得到由单调区域的最长部分和组成的Seq[Int]
。例如:
val s = Seq(1, 2, 4, 3, 2, -1, 0, 6, 8)
groupMonotionic(s) = Seq(1 + 2 + 4, 3 + 2 + (-1), 0 + 6 + 8)
我一直在寻找带有签名fold(z: B)((B, T) => B, (T, T) => Boolean)
的条件折叠之类的方法,其中谓词说明了终止当前总和聚合的位置,但是看来{{1}的子特性层次结构中没有类似的东西}。
使用Scala Collection API而不使用可变变量的解决方案是什么?
答案 0 :(得分:3)
以下是其中一种方法(使用Scala 2.13
的{{3}}):
// val items = Seq(1, 2, 4, 3, 2, -1, 0, 6, 8)
items match {
case first :: _ :: _ => // If there are more than 2 items
List
.unfold(items.sliding(2).toList) { // We slid items to work on pairs of consecutive items
case Nil => // No more items to unfold
None // None signifies the end of the unfold
case rest @ Seq(a, b) :: _ => // We span based on the sign of a-b
Some(rest.span(x => (x.head - x.last).signum == (a-b).signum))
}
.map(_.map(_.last)) // back from slided pairs
match { case head :: rest => (first :: head) :: rest }
case _ => // If there is 0 or 1 item
items.map(List(_))
}
// List(List(1, 2, 4), List(3, 2, -1), List(0, 6, 8))
只要展开功能提供List.unfold
, Some
就会迭代。它从初始状态开始,初始状态是要展开的项目列表。在每次迭代中,我们根据标题两个元素差异的符号span
来表示状态(剩余元素展开)。展开元素是具有相同单调性的标题项目,展开状态成为其他剩余元素。
List#span
将列表分成一个元组,其第一部分包含与所应用谓词匹配的元素,直到谓词停止有效。元组的第二部分包含其余元素。完全符合List.unfold
的展开函数的预期收益类型,即Option[(A, S)]
(在这种情况下为Option[(List[Int], List[Int])]
)。
Int.signum
返回-1
,0
或1
,具体取决于所应用整数的符号。
请注意,第一项必须放回结果中,因为它没有祖先确定其信号(match { case head :: rest => (first :: head) :: rest }
)。
要应用归约函数(在这种情况下为总和),我们可以映射最终结果:.map(_.sum)
答案 1 :(得分:3)
使用cats
在Scala 2.13+中工作
import scala.util.chaining._
import cats.data._
import cats.implicits._
val s = List(1, 2, 4, 3, 2, -1, 0, 6, 8)
def isLocalExtrema(a: List[Int]) =
a.max == a(1) || a.min == a(1)
implicit class ListOps[T](ls: List[T]) {
def multiSpanUntil(f: T => Boolean): List[List[T]] = ls.span(f) match {
case (h, Nil) => List(h)
case (h, t) => (h ::: t.take(1)) :: t.tail.multiSpanUntil(f)
}
}
def groupMonotionic(groups: List[Int]) = groups match {
case Nil => Nil
case x if x.length < 3 => List(groups.sum)
case _ =>
groups
.sliding(3).toList
.map(isLocalExtrema)
.pipe(false :: _ ::: List(false))
.zip(groups)
.multiSpanUntil(!_._1)
.pipe(Nested.apply)
.map(_._2)
.value
.map(_.sum)
}
println(groupMonotionic(s))
//List(7, 4, 14)
答案 2 :(得分:2)
这是使用foldLeft
通过Tuple3累加器(listOfLists, prevElem, prevTrend)
遍历数字列表的一种方法,该累加器存储previous element
和previous trend
以有条件地将list of lists
组装到当前迭代:
val list = List(1, 2, 4, 3, 2, -1, 0, 6, 8)
val isUpward = (a: Int, b: Int) => a < b
val initTrend = isUpward(list.head, list.tail.head)
val monotonicLists = list.foldLeft( (List[List[Int]](), list.head, initTrend) ){
case ((lol, prev, prevTrend), curr) =>
val currTrend = isUpward(curr, prev)
if (currTrend == prevTrend)
((curr :: lol.head) :: lol.tail , curr, currTrend)
else
(List(curr) :: lol , curr, currTrend)
}._1.reverse.map(_.reverse)
// monotonicLists: List[List[Int]] = List(List(1, 2, 4), List(3, 2, -1), List(0, 6, 8))
总结单个嵌套列表:
monotonicLists.map(_.sum)
// res1: List[Int] = List(7, 4, 14)