【发布时间】:2011-03-14 14:06:16
【问题描述】:
我有一个月份的天数列表:
val days = List(31, 28, 31, ...)
我需要返回一个包含累计天数的列表:
val cumDays = List(31, 59, 90)
我曾想过使用折叠运算符:
(0 /: days)(_ + _)
但这只会返回最终结果(365),而我需要中间结果列表。
无论如何我可以优雅地做到这一点?
【问题讨论】:
标签: scala
我有一个月份的天数列表:
val days = List(31, 28, 31, ...)
我需要返回一个包含累计天数的列表:
val cumDays = List(31, 59, 90)
我曾想过使用折叠运算符:
(0 /: days)(_ + _)
但这只会返回最终结果(365),而我需要中间结果列表。
无论如何我可以优雅地做到这一点?
【问题讨论】:
标签: scala
对于任何:
val s:Seq[Int] = ...
您可以使用其中之一:
s.tail.scanLeft(s.head)(_ + _)
s.scanLeft(0)(_ + _).tail
或其他答案中提出的折叠但是...... 请注意,Landei 的解决方案很棘手,您应该避免使用它。
注意
s.map { var s = 0; d => {s += d; s}}
//works as long `s` is strict collection
val s2:Seq[Int] = s.view //still seen as Seq[Int]
s2.map { var s = 0; d => {s += d; s}}
//makes really weird things!
//Each value'll be different whenever you'll access it!
我应该在 Landei 的回答下方评论这一点,但我不能:(。
【讨论】:
您还可以创建一个连接两个列表的 monoid 类,同时将第一个列表的最后一个值添加到第二个列表中。不涉及可变变量和折叠:
case class CumSum(v: List[Int]) { def +(o: CumSum) = CumSum(v ::: (o.v map (_ + v.last))) }
defined class CumSum
scala> List(1,2,3,4,5,6) map {v => CumSum(List(v))} reduce (_ + _)
res27: CumSum = CumSum(List(1, 3, 6, 10, 15, 21))
【讨论】:
你可以简单地执行它:
daysInMonths.foldLeft((0, List[Int]()))
{(acu,i)=>(i+acu._1, i+acu._1 :: acu._2)}._2.reverse
【讨论】:
我不确定为什么每个人似乎都坚持使用某种折叠,而您基本上想将值映射到累积值...
val daysInMonths = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val cumulated = daysInMonths.map{var s = 0; d => {s += d; s}}
//--> List[Int] = List(31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
【讨论】:
foldLeft 的定义使用vars,所以在这里使用它们就可以了。有时可变数据更高效、更清晰,这就是一个很好的例子。
map,并且作用域规则是包含可变变量的。我在其他地方没见过,但它打开了很多门......
d => {s += d; s}。然而,这个 lambda 包含一个来自其生成上下文的变量 s,它会“捕获”它,因为 Scala 不仅有函数指针之类的东西,还有真正的闭包。参见例如stackoverflow.com/questions/208835/…
将您的列表折叠成一个新列表。在每次迭代中,附加一个值,该值是头部 + 下一个输入的总和。然后把整个事情倒过来。
scala> val daysInMonths = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
daysInMonths: List[Int] = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
scala> daysInMonths.foldLeft(Nil: List[Int]) { (acc,next) =>
| acc.firstOption.map(_+next).getOrElse(next) :: acc
| }.reverse
res1: List[Int] = List(31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
【讨论】:
适用于 2.7.7:
def stepSum (sums: List [Int], steps: List [Int]) : List [Int] = steps match {
case Nil => sums.reverse.tail
case x :: xs => stepSum (sums.head + x :: sums, steps.tail) }
days
res10: List[Int] = List(31, 28, 31, 30, 31)
stepSum (List (0), days)
res11: List[Int] = List(31, 59, 90, 120, 151)
【讨论】:
Scala 2.8 具有 scanLeft 和 scanRight 方法,它们正是这样做的。
对于 2.7,您可以像这样定义自己的 scanLeft:
def scanLeft[a,b](xs:Iterable[a])(s:b)(f : (b,a) => b) =
xs.foldLeft(List(s))( (acc,x) => f(acc(0), x) :: acc).reverse
然后像这样使用它:
scala> scanLeft(List(1,2,3))(0)(_+_)
res1: List[Int] = List(0, 1, 3, 6)
【讨论】:
List(1, 2, 3).scanLeft(0)(_+_)。
折叠成一个列表而不是一个整数。使用对(带有累加值的部分列表,带有最后一个总和的累加器)作为折叠中的状态。
【讨论】: