Scala: Cálculo de la suma móvil de una lista con una ventana fija

8

Soy nuevo en Scala y quiero calcular una suma móvil con una ventana fija para una lista.

Por ejemplo: dados los valores de la lista (1.0, 2.0, 3.0, 6.0, 7.0, 8.0, 12.0, 9.0, 4.0, 1.0), y el período 4, la función debería devolver: (1.0, 3.0, 6.0, 12.0, 18.0, 24.0, 33.0, 36.0, 33.0, 26.0)

Si list.size <punto, simplemente devuelve la suma acumulativa.

He hecho algunos intentos

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
  }
}

Sin embargo, tengo

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

lo cual no es correcto No quiero usar Pyspark para obtener los resultados. Alguien puede ayudar?

Muchas gracias.

FlyUFalcon
fuente
Prueba el slidingmétodo
Seth Tisue
1
Noto que la ventana crece (1er elemento, 1er 2 elementos, 1er 3 elementos, etc.) pero no se contrae (últimos 4 elementos, últimos 3 elementos, últimos 2 elementos, etc.). ¿Es eso intencional?
jwvh

Respuestas:

5
  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
  }
Usuario9123
fuente
increíble 👏 buena solución !!!!!
Raman Mishra
2
tenga en cuenta que esto vuelve List(0.0)cuando values = Seq()yperiod > 1
CervEd
@CervEd gracias por su aviso, corríjalo
User9123
@ User9123, puede haber más. Tuve que hacer algunas acrobacias en mi respuesta
CervEd
3

Aquí hay una forma de abordarlo.

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

pruebas:

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)
jwvh
fuente
2

Esta es otra forma de hacer esto:

  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)
Raman Mishra
fuente
1

Otro enfoque, similar a la respuesta de @ User9123

La diferencia es que no calcula la suma de todos los elementos en la ventana deslizante, sino que resta el valor de la última cabeza de la ventana de su suma y agrega el valor de la siguiente cabeza de la ventana para obtener la siguiente suma. Esto debería ser más eficiente para ventanas grandes.

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
  }
}

También agregué algunos protectores para casos especiales que deben manejarse y lo convertí en una función genérica para todos los Numerictipos.

Aquí hay un ejemplo en ejecución con algunos casos de prueba.

CervEd
fuente