Scala:使用固定窗口计算列表的移动总和

8

我是Scala的新手,想要计算列表中固定窗口的移动总和。

例如:给定值列表(1.0, 2.0, 3.0, 6.0, 7.0, 8.0, 12.0, 9.0, 4.0, 1.0),并且指定周期为4,函数应该返回: (1.0, 3.0, 6.0, 12.0, 18.0, 24.0, 33.0, 36.0, 33.0, 26.0)

如果列表大小小于周期,则返回累积总和。

我已经尝试过一些方法。

def mavg(values: List[Double], period: Int): List[Double] = {
  if (values.size <= period) (values.sum ) :: List.fill(period -1)(values.sum ) else {
      val rest: List[Double] = mavg(values.tail, period)
      (rest.head + ((values.head - values(period)))):: rest
  }
}

然而,我收到了

List(12.0, 18.0, 24.0, 33.0, 36.0, 33.0, 26.0, 26.0, 26.0, 26.0

这是不正确的。我不想使用Pyspark来获取结果。有人可以帮忙吗?

非常感谢。


尝试使用“滑动”方法。 - Seth Tisue
1
我注意到窗口会变大(第一个元素,前两个元素,前三个元素等),但它不会缩小(最后四个元素,最后三个元素,最后两个元素等)。这是有意为之吗? - jwvh
4个回答

6
  def mavg(values: Seq[Double], period: Int): Seq[Double] = {
    (Seq.fill(math.min(period - 1, values.length))(0.0) ++ values) // padding zeros
      .sliding(period)                  
      .map(_.sum)
      .toSeq
  }

2
请注意,当 values = Seq()period > 1 时,此代码将返回 List(0.0) - CervEd
@User9123,可能还有更多。我在我的回答中必须自己做一些杂技。 - CervEd

3

这里有一种解决方法。

def mavg(values: List[Double], period: Int): List[Double] =
  values.inits    //shrinking list of inits
        .toList   //result type
        .reverse  //growing list of inits
        .tail     //drop the empty one
        .map(_.takeRight(period).sum) //sum the window

测试:

mavg(List(1.0, 2.0, 3.0, 6.0, 7.0, 8.0, 12.0, 9.0, 4.0, 1.0), 4)
//res0: List[Double] = List(1.0, 3.0, 6.0, 12.0, 18.0, 24.0, 33.0, 36.0, 33.0, 26.0)

2
这是另一种实现方法:

  val l = List(1.0, 2.0, 3.0, 6.0, 7.0, 8.0, 12.0, 9.0, 4.0, 1.0,5.0,1.0,2.0)
  def mavg(step: Int, list: List[Double], ans: List[Double] = List.empty[Double], splitCount: Int = 0): List[Double] = {
    if (list.length > 1) {
      mavg(step - 1, list.take(step), list.sliding(step, 1).toList.map(_.sum) ::: ans, splitCount + 1)
    } else {
      ans.splitAt(splitCount + 2)._1.sliding(1, 2).toList.flatten ::: ans.drop(splitCount + 2)
    }
  }

  val ans = mavg(4, l)
  println(ans)

1

另一种方法,类似于@User9123的答案

区别在于它不计算滑动窗口中所有元素的总和,而是将下一个滚动总和的值减去上一个窗口头的值,并添加下一个窗口头的值。对于大窗口,这应该更有效率。

def rollingSum[N](values: Seq[N], period: Int)(
    implicit num: Numeric[N]
): Seq[N] = {
  import num._
  values match {
    case values if period == 1 => values // Can't slide on period 1
    case head :: tail if period < values.size =>
      (Seq.fill(period - 2)(num.zero) ++ (values)) // zero padding
        .sliding(period)
        .foldLeft((num.zero, Seq(head))) { // Use a tuple to store previous head
          case ((prevHead, acc), y) => {
            (y.head, acc :+ acc.last - prevHead + y.last) // do the magic
          }
        }
        ._2 // only return the result
    case head :: tail => tail.scanLeft(head)(_ + _) // Regular cummulative sum
    case Nil          => Nil
  }
}

我还为需要处理的特殊情况添加了一些保护,并将其制作成适用于所有 Numeric 类型的通用函数。

这里 是一些测试案例的运行示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接