继续进行有关以前计算的信息

时间:2012-05-14 11:09:19

标签: scala functional-programming

我是函数式编程的新手,所以使用函数式方法似乎很难解决一些问题。

假设我有一个数字列表,例如1到10.000,我想得到列表中最多总数为n的项目(假设为100)。所以,它会得到数字,直到它们的总和大于100。

在命令式编程中,解决这个问题是微不足道的,因为我可以在每次交互中保留一个变量,并在达到目标后停止。

但是我怎么能在函数式编程中做同样的事情呢?由于sum函数在已完成的列表上运行,并且我仍然没有完成的列表,我怎样才能“继续”计算?

如果总计被懒惰计算,我可以写出类似的东西:

           (1 to 10000).sum.takeWhile(_ < 100)

P.S.:Even虽然任何答案都会受到赞赏,但我希望每次都不计算总和,因为显然命令式版本在速度方面会更加优化。

编辑:

我知道我可以将命令式循环方法“转换”为功能递归函数。我更感兴趣的是找到一个现有的库函数是否可以为我提供一种方法,让我不需要在每次需要时写一个。

4 个答案:

答案 0 :(得分:9)

使用Stream

scala> val ss = Stream.from(1).take(10000)
ss: scala.collection.immutable.Stream[Int] = Stream(1, ?)

scala> ss.scanLeft(0)(_ + _)
res60: scala.collection.immutable.Stream[Int] = Stream(0, ?)

scala> res60.takeWhile(_ < 100).last
res61: Int = 91

修改

获取组件也不是很棘手。这就是你如何做到的:

scala> ss.scanLeft((0, Vector.empty[Int])) { case ((sum, compo), cur) => (sum + cur, compo :+ cur) }
res62: scala.collection.immutable.Stream[(Int, scala.collection.immutable.Vector[Int])] = Stream((0,Vector()), ?)

scala> res62.takeWhile(_._1 < 100).last
res63: (Int, scala.collection.immutable.Vector[Int]) = (91,Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13))

元组的第二部分是你想要的结果。

显而易见,在这种情况下,构建矢量是浪费的。相反,我们只能存储有助于求和的流中的最后一个数字。

scala> ss.scanLeft(0)(_ + _).zipWithIndex
res64: scala.collection.immutable.Stream[(Int, Int)] = Stream((0,0), ?)

scala> res64.takeWhile(_._1 < 100).last._2
res65: Int = 13

答案 1 :(得分:1)

我这样做的方法是使用递归。在每次通话时,添加下一个号码。您的基本情况是当总和大于100时,此时您将一直返回堆栈。你需要一个辅助函数来进行实际的递归,但这没什么大不了的。

答案 2 :(得分:1)

使用“功能”方法也不难 使用递归,而不是将您的状态保存在您变异的局部变量中,将其保存在参数中并返回值。

因此,要返回总和最多为N的列表的最长初始部分:

  1. 如果列表为空,则表示您已完成;返回空列表。
  2. 如果列表的头部大于N,那么你就完成了;返回空列表。
  3. 否则,让H成为列表的头部 我们现在需要的是列表的 tail 的最初部分,其总和最多为N - H,然后我们可以“将”H“收缩”到该列表中,我们'重做。
    我们可以使用与我们目前使用的相同的过程递归计算这个,所以这是一个简单的步骤。
  4. 一个简单的伪代码解决方案:

    sum_to (n, ls) = if isEmpty ls or n < (head ls)
                     then Nil
                     else (head ls) :: sum_to (n - head ls, tail ls)
    
    sum_to(100, some_list)
    

答案 3 :(得分:0)

所有只需要一次通过序列的序列操作就可以使用折叠来实现,就像它有时被调用一样。 我发现自己经常使用折叠,因为我习惯了函数式编程

所以这里奇怪的一种可能的方法 使用空集合作为初始值并根据此策略折叠 鉴于已处理的集合和新的值检查它们的总和是否足够低,然后将该值花在集合上,否则什么都不做

该解决方案效率不高但我想强调以下内容 地图折叠过滤器zip等是习惯于函数编程的方法尝试尽可能多地使用它们而不是loping构造或递归函数,你的代码将更具说明性和功能性