【问题标题】:In Scala, how do I fold a List and return the intermediate results?在 Scala 中,如何折叠 List 并返回中间结果?
【发布时间】:2011-03-14 14:06:16
【问题描述】:

我有一个月份的天数列表:

val days = List(31, 28, 31, ...)

我需要返回一个包含累计天数的列表:

val cumDays = List(31, 59, 90)

我曾想过使用折叠运算符:

(0 /: days)(_ + _)

但这只会返回最终结果(365),而我需要中间结果列表。

无论如何我可以优雅地做到这一点?

【问题讨论】:

    标签: scala


    【解决方案1】:

    对于任何:

    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 的回答下方评论这一点,但我不能:(。

    【讨论】:

      【解决方案2】:

      您还可以创建一个连接两个列表的 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))
      

      【讨论】:

        【解决方案3】:

        你可以简单地执行它:

        daysInMonths.foldLeft((0, List[Int]()))
                             {(acu,i)=>(i+acu._1, i+acu._1 :: acu._2)}._2.reverse
        

        【讨论】:

          【解决方案4】:

          我不确定为什么每个人似乎都坚持使用某种折叠,而您基本上想将值映射到累积值...

          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)
          

          【讨论】:

          • 因为如果有一个使用折叠的解决方案和另一个使用可变变量的解决方案,大多数人(具有 FP 背景)会选择折叠。
          • 我看不出这个有什么问题:var不暴露,解法通俗易懂,简短易读,计算效率高。当然,可变性的使用应该受到限制而不是过度使用,但不变性只是一种工具而不是教条 - 至少在任何提供可变性的语言中是这样。
          • foldLeft 的定义使用vars,所以在这里使用它们就可以了。有时可变数据更高效、更清晰,这就是一个很好的例子。
          • 您知道看到这个解决方案有什么特别好的地方吗?了解多个表达式可以作为参数传递给map,并且作用域规则是包含可变变量的。我在其他地方没见过,但它打开了很多门......
          • @Jim Pivarski :准确地说,map 方法接收的参数只是块的最后一条语句,d => {s += d; s}。然而,这个 lambda 包含一个来自其生成上下文的变量 s,它会“捕获”它,因为 Scala 不仅有函数指针之类的东西,还有真正的闭包。参见例如stackoverflow.com/questions/208835/…
          【解决方案5】:

          将您的列表折叠成一个新列表。在每次迭代中,附加一个值,该值是头部 + 下一个输入的总和。然后把整个事情倒过来。

          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)
          

          【讨论】:

            【解决方案6】:

            适用于 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)
            

            【讨论】:

              【解决方案7】:

              Scala 2.8 具有 scanLeftscanRight 方法,它们正是这样做的。

              对于 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)(_+_)
              【解决方案8】:

              折叠成一个列表而不是一个整数。使用对(带有累加值的部分列表,带有最后一个总和的累加器)作为折叠中的状态。

              【讨论】:

              • 基本上就是sepp2k给出的scan的定义。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-12-02
              • 1970-01-01
              • 2014-04-16
              • 2014-04-13
              • 1970-01-01
              相关资源
              最近更新 更多