scala中相邻项目的平均值
Average for adjacent items in scala
我有一个seq
val seq = Seq(1, 9, 5, 4, 3, 5, 5, 5, 8, 2)
我想得到每个相邻(左右)数字的平均值,这意味着在上面的例子中有以下计算:
[(1+9)/2, (1+9+5)/3, (9+5+4)/3, (5+4+3)/3, (4+3+5)/3, (3+5+5)/3, (5+5+5)/3, (5+5+8)/3, (5+8+2)/3, (8+2)/2]
其他例子是:
Seq() shouldBe Seq()
Seq(3) shouldBe Seq(3.0d)
Seq(1, 4) shouldBe Seq(2.5d, 2.5d)
Seq(1, 9, 5, 4, 3, 5, 5, 5, 8, 2) shouldBe Seq(5.0, 5.0, 6.0, 4.0, 4.0, 13.0 / 3, 5.0, 6.0, 5.0, 5.0)
我能够得到:numbers.sliding(2, 1).map(nums => nums.sum.toDouble / nums.length).toSeq
。但是它不考虑之前的值。
我试着用 foldLeft
来做 - 它也很麻烦。
有没有简单的方法来做到这一点?我错过了什么?
老实说,我认为使用简单的 (虽然有点长) tail-recursive 算法更容易解决这类问题。
def adjacentAverage(data: List[Int]): List[Double] = {
@annotation.tailrec
def loop(remaining: List[Int], acc: List[Double], previous: Int): List[Double] =
remaining match {
case x :: y :: xs =>
loop(
remaining = y :: xs,
((previous + x + y).toDouble / 3.0d) :: acc,
previous = x
)
case x :: Nil =>
(((previous + x).toDouble / 2.0d) :: acc).reverse
}
data match {
case x :: y :: xs => loop(remaining = y :: xs, acc = ((x + y).toDouble / 2.0d) :: Nil, previous = x)
case x :: Nil => x.toDouble :: Nil
case Nil => Nil
}
}
可以看到运行here.
如果您想要不同大小的滑动 window 怎么办,例如 4 或 7 或...?挑战是获得 build-up、(1), (1,2), (1,2,3), (1,2,3,4), ...
和 tail-off、..., (6,7,8,9), (7,8,9), (8,9), (9)
。
def windowAvg(input: Seq[Int], windowSize: Int): Seq[Double] =
if (input.isEmpty || windowSize < 1) Seq()
else {
val windows = input.sliding(windowSize).toSeq
val buildUp = windows.head.inits.toSeq.tail.reverse.tail
val tailOff = windows.last.tails.toSeq.tail.init
(buildUp ++ windows ++ tailOff).map(x => x.sum.toDouble / x.length)
}
如果您真的需要 trim 关闭结果中的开头和结尾 single-number 条目,那么我将把它留作 reader.[=13 的练习=]
我通过 foldLeft
的繁琐解决方案(没有火箭科学)
def adjacentAverage(numbers: Seq[Int]): Seq[Double] = numbers.foldLeft(("x", Seq[Double](), 0)) {(acc, num) => acc._1 match {
case "x" => if (numbers.isEmpty) ("x", Seq(), acc._3 + 1) else if (numbers.length == 1) ("x", Seq(num.toDouble), acc._3 + 1) else (num.toString, acc._2 :+ ((num.toDouble + numbers(acc._3 + 1).toDouble) / 2.0), acc._3 + 1)
case _ => (num.toString, try {acc._2 :+ ((acc._1.toDouble + num.toDouble + numbers(acc._3 + 1).toDouble) / 3.0)} catch {case e: IndexOutOfBoundsException => acc._2 :+ ((acc._1.toDouble + num.toDouble) / 2.0) }, acc._3 + 1)
}}._2
我有一个seq
val seq = Seq(1, 9, 5, 4, 3, 5, 5, 5, 8, 2)
我想得到每个相邻(左右)数字的平均值,这意味着在上面的例子中有以下计算:
[(1+9)/2, (1+9+5)/3, (9+5+4)/3, (5+4+3)/3, (4+3+5)/3, (3+5+5)/3, (5+5+5)/3, (5+5+8)/3, (5+8+2)/3, (8+2)/2]
其他例子是:
Seq() shouldBe Seq()
Seq(3) shouldBe Seq(3.0d)
Seq(1, 4) shouldBe Seq(2.5d, 2.5d)
Seq(1, 9, 5, 4, 3, 5, 5, 5, 8, 2) shouldBe Seq(5.0, 5.0, 6.0, 4.0, 4.0, 13.0 / 3, 5.0, 6.0, 5.0, 5.0)
我能够得到:numbers.sliding(2, 1).map(nums => nums.sum.toDouble / nums.length).toSeq
。但是它不考虑之前的值。
我试着用 foldLeft
来做 - 它也很麻烦。
有没有简单的方法来做到这一点?我错过了什么?
老实说,我认为使用简单的 (虽然有点长) tail-recursive 算法更容易解决这类问题。
def adjacentAverage(data: List[Int]): List[Double] = {
@annotation.tailrec
def loop(remaining: List[Int], acc: List[Double], previous: Int): List[Double] =
remaining match {
case x :: y :: xs =>
loop(
remaining = y :: xs,
((previous + x + y).toDouble / 3.0d) :: acc,
previous = x
)
case x :: Nil =>
(((previous + x).toDouble / 2.0d) :: acc).reverse
}
data match {
case x :: y :: xs => loop(remaining = y :: xs, acc = ((x + y).toDouble / 2.0d) :: Nil, previous = x)
case x :: Nil => x.toDouble :: Nil
case Nil => Nil
}
}
可以看到运行here.
如果您想要不同大小的滑动 window 怎么办,例如 4 或 7 或...?挑战是获得 build-up、(1), (1,2), (1,2,3), (1,2,3,4), ...
和 tail-off、..., (6,7,8,9), (7,8,9), (8,9), (9)
。
def windowAvg(input: Seq[Int], windowSize: Int): Seq[Double] =
if (input.isEmpty || windowSize < 1) Seq()
else {
val windows = input.sliding(windowSize).toSeq
val buildUp = windows.head.inits.toSeq.tail.reverse.tail
val tailOff = windows.last.tails.toSeq.tail.init
(buildUp ++ windows ++ tailOff).map(x => x.sum.toDouble / x.length)
}
如果您真的需要 trim 关闭结果中的开头和结尾 single-number 条目,那么我将把它留作 reader.[=13 的练习=]
我通过 foldLeft
的繁琐解决方案(没有火箭科学)
def adjacentAverage(numbers: Seq[Int]): Seq[Double] = numbers.foldLeft(("x", Seq[Double](), 0)) {(acc, num) => acc._1 match {
case "x" => if (numbers.isEmpty) ("x", Seq(), acc._3 + 1) else if (numbers.length == 1) ("x", Seq(num.toDouble), acc._3 + 1) else (num.toString, acc._2 :+ ((num.toDouble + numbers(acc._3 + 1).toDouble) / 2.0), acc._3 + 1)
case _ => (num.toString, try {acc._2 :+ ((acc._1.toDouble + num.toDouble + numbers(acc._3 + 1).toDouble) / 3.0)} catch {case e: IndexOutOfBoundsException => acc._2 :+ ((acc._1.toDouble + num.toDouble) / 2.0) }, acc._3 + 1)
}}._2