我是函数式编程的新手,所以使用函数式方法似乎很难解决一些问题。
假设我有一个数字列表,例如1到10.000,我想得到列表中最多总数为n的项目(假设为100)。所以,它会得到数字,直到它们的总和大于100。
在命令式编程中,解决这个问题是微不足道的,因为我可以在每次交互中保留一个变量,并在达到目标后停止。
但是我怎么能在函数式编程中做同样的事情呢?由于sum函数在已完成的列表上运行,并且我仍然没有完成的列表,我怎样才能“继续”计算?
如果总计被懒惰计算,我可以写出类似的东西:
(1 to 10000).sum.takeWhile(_ < 100)
P.S.:Even虽然任何答案都会受到赞赏,但我希望每次都不计算总和,因为显然命令式版本在速度方面会更加优化。
编辑:
我知道我可以将命令式循环方法“转换”为功能递归函数。我更感兴趣的是找到一个现有的库函数是否可以为我提供一种方法,让我不需要在每次需要时写一个。
答案 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
的列表的最长初始部分:
N
,那么你就完成了;返回空列表。H
成为列表的头部
我们现在需要的是列表的 tail 的最初部分,其总和最多为N - H
,然后我们可以“将”H
“收缩”到该列表中,我们'重做。一个简单的伪代码解决方案:
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构造或递归函数,你的代码将更具说明性和功能性