逐个减少顺序

时间:2019-06-22 08:40:41

标签: scala collections

我有一个序列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而不使用可变变量的解决方案是什么?

3 个答案:

答案 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返回-101,具体取决于所应用整数的符号。

请注意,第一项必须放回结果中,因为它没有祖先确定其信号(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 elementprevious 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)