Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >如何在Scala中使用Stream.cons编写无泄漏的尾递归函数?

如何在Scala中使用Stream.cons编写无泄漏的尾递归函数?
EN

Stack Overflow用户
提问于 2012-09-21 11:32:35
回答 2查看 2.7K关注 0票数 14

当编写一个在Stream(s)上操作的函数时,有不同的递归概念。第一个简单的意义在编译器级别上不是递归的,因为尾部不是立即计算的,所以函数立即返回,但返回的流是递归的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final def simpleRec[A](as: Stream[A]): Stream[B] = 
  if (a.isEmpty) Stream.empty              
  else someB(a.head) #:: simpleRec(a.tail) 

上面的递归概念不会引起任何问题。第二个在编译器级别上是真正的尾递归:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@tailrec
final def rec[A](as: Stream[A]): Stream[B] = 
  if (a.isEmpty) Stream.empty              // A) degenerated
  else if (someCond) rec(a.tail)           // B) tail recursion
  else someB(a.head) #:: rec(a.tail)       // C) degenerated

这里的问题是,即使没有执行实际的调用,编译器也会将C)的情况检测为非tailrec调用。这可以通过将流尾部分解到助手函数中来避免:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@tailrec
final def rec[A](as: Stream[A]): Stream[B] = 
  if (a.isEmpty) Stream.empty              
  else if (someCond) rec(a.tail)          // B)
  else someB(a.head) #:: recHelp(a.tail)  

@tailrec
final def recHelp[A](as: Stream[A]): Stream[B] = 
  rec(as)

在编译时,这种方法最终会导致内存泄漏。由于尾递归rec最终是从recHelp函数调用的,因此recHelp函数的堆栈框架保留了对蒸汽头的引用,并且在rec调用返回之前不让流被垃圾收集,这可能会很长(就递归步骤而言),具体取决于对B)的调用次数。

请注意,即使在无助的情况下,如果编译器允许@tailrec,内存泄漏可能仍然存在,因为惰性流尾部实际上会创建一个匿名对象,其中包含对流头部的引用。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-09-29 10:41:57

正如您所暗示的,问题是在粘贴的代码中,filterHelp函数保留了头部(因此您的解决方案删除了它)。

最好的答案是简单地避免这种令人惊讶的行为,使用Scalaz EphemeralStream,并看到它既不是oom,而且运行速度要快得多,因为它对gc更好。使用它并不总是那么简单,例如head是一个() => A而不是A,没有提取器等,但它都面向一个目标,可靠的流使用。

您的filterHelper函数通常不必关心它是否保留引用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import scalaz.EphemeralStream

@scala.annotation.tailrec
def filter[A](s: EphemeralStream[A], f: A => Boolean): EphemeralStream[A] = 
  if (s.isEmpty) 
    s
  else
    if (f(s.head())) 
      EphemeralStream.cons(s.head(), filterHelp(s.tail() , f) )
    else
      filter(s.tail(), f)

def filterHelp[A](s: EphemeralStream[A], f: A => Boolean) =
  filter(s, f)

def s1 = EphemeralStream.range(1, big)

我甚至要说,除非你有令人信服的理由使用流(其他库依赖等),否则就坚持使用EphemeralStream,那里就不会有太多的惊喜。

票数 2
EN

Stack Overflow用户

发布于 2012-09-21 11:32:35

一种可能的解决方法是使recHelp方法不包含对流标头的引用。这可以通过将一个包装的流传递给它,并改变包装器以擦除其中的引用来实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@tailrec
final def rec[A](as: Stream[A]): Stream[B] = 
  if (a.isEmpty) Stream.empty              
  else if (someCond) rec(a.tail)          
  else {
    // don't inline and don't define as def,
    // or anonymous lazy wrapper object would hold reference
    val tailRef = new AtomicReference(a.tail)
    someB(a.head) #:: recHelp(tailRef)  
  }

@tailrec
final def recHelp[A](asRef: AtomicReference[Stream[A]]): Stream[B] = 
  // Note: don't put the content of the holder into a local variable
  rec(asRef.getAndSet(null))

AtomicReference只是为了方便,在这种情况下不需要原子性,任何简单的holder对象都可以。

还要注意,由于recHelp被包装在流Cons尾部中,因此它只会被计算一次,并且Cons还负责同步。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12529697

复制
相关文章
模板:大数阶乘
#include <stdio.h> int main() { const int maxn = ...; //n的阶乘所得的值的大致位数 int a[maxn];//储存每一位所得到的数 int temp,digit,n,i,j=0;//temp每次的得数 digit每次得数的位数 scanf("%d",&n); a[0]=1;//从1开始乘 digit=1;//位数从第一位开始 for(i=2;i<=n;i++)
_DIY
2019/09/11
5150
阶乘计算【 数组模拟阶乘 】
n!可能很大,而计算机能表示的整数范围有限,需要使用高精度计算的方法。使用一个数组A来表示一个大整数a,A[0]表示a的个位,A[1]表示a的十位,依次类推。
Lokinli
2023/03/09
7660
C++ 模板元编程简介
模板元编程(Template Metaprogramming,TMP)是编写生成或操纵程序的程序,也是一种复杂且功能强大的编程范式(Programming Paradigm)。C++模板给C++提供了元编程的能力,但大部分用户对 C++ 模板的使用并不是很频繁,大致限于泛型编程,在一些系统级的代码,尤其是对通用性、性能要求极高的基础库(如 STL、Boost)几乎不可避免在大量地使用 C++ 模板以及模板元编程。
恋喵大鲤鱼
2018/09/27
6.9K0
C++ 模板元编程简介
计算阶乘之和
阶乘是数学里的一种术语;阶乘指从1乘以2乘以3乘以4一直乘到所要求的数;在表达阶乘时,用“!”来表示。乘一般都难以计算,因为数值较大,而用python就不用当心阶乘的计算结果会溢出。
算法与编程之美
2022/02/17
6460
计算阶乘之和
Python计算阶乘
通过用户输入数字计算阶乘 1.获取用户输入的数字 num = int(input("请输入一个数字: ")) factorial = 1 2.判断数字 负数没有阶乘 0的阶乘还是0 if num < 0: print("抱歉,负数没有阶乘") elif num == 0: print("0 的阶乘为 1") else: for i in range(1, num + 1): factorial = factorial * i print("%d 的阶乘为 %d
织幻妖
2021/06/27
1.5K0
Python计算阶乘
JAVA编程--阶乘运算
代码如下: import java.math.BigInteger; import java.util.ArrayList; public class doFactorial { public static void main(String[] args) { int number=5; System.out.println("方法一算得"+number+"的阶乘为:"+Wayone(number)); System.out.prin
用户8711264
2023/03/21
6590
基础练习 阶乘计算
  n!可能很大,而计算机能表示的整数范围有限,需要使用高精度计算的方法。使用一个数组A来表示一个大整数a,A[0]表示a的个位,A[1]表示a的十位,依次类推。   将a乘以一个整数k变为将数组A的每一个元素都乘以k,请注意处理相应的进位。   首先将a设为1,然后乘2,乘3,当乘到n时,即得到了n!的值。
刘开心_1266679
2019/02/14
8850
php递归函数详解_用php递归函数实现阶乘计算
white=imagecolorallocate(im,0xFF,0xFF,0xFF);
全栈程序员站长
2022/09/22
2.8K0
元编程(用程序写代码)
元编程的主要思想是用程序在运行时写代码,再在运行时在编译代码。 元编程又被称为两级编程 (two-level programming),生成式编程 (generative programming) 或 模板元编程 (template metaprogramming)
sofu456
2022/05/06
3900
Python|用python解决阶乘问题
阶乘是我们在很多的数学问题中会遇到的,但是如果我们需要一个很大的数的阶乘,那么自己算起来就会很麻烦,那么我们就能用python来解决这个问题。让阶乘编程一个简单的问题
算法与编程之美
2020/02/25
1.3K0
数的阶乘计算器
/* 功能:数的阶乘计算器 日期:2013-4-19 */ #include <stdio.h> #include <stdlib.h> #include<math.h>
WindCoder
2018/09/20
1.1K0
蓝桥杯 基础练习 阶乘计算
n!可能很大,而计算机能表示的整数范围有限,需要使用高精度计算的方法。使用一个数组A来表示一个大整数a,A[0]表示a的个位,A[1]表示a的十位,依次类推。   将a乘以一个整数k变为将数组A的每一个元素都乘以k,请注意处理相应的进位。   首先将a设为1,然后乘2,乘3,当乘到n时,即得到了n!的值。
Meng小羽
2019/12/23
6370
Python应用之计算阶乘
当 m 是自然数时,表示不超过 m 且与 m 有相同奇偶性的所有正整数的乘积。如:
芯动大师
2022/11/15
1.7K0
Python应用之计算阶乘
如何花式计算20的阶乘?
先来看看我的回答:https://www.zhihu.com/question/365763395/answer/2070162652
godweiyang
2021/09/08
1.3K0
73-递归函数计算阶乘
递归函数就是在函数内部继续调用自己。 def func(n): # 5 if n == 1: return n return n * func(n - 1) # 5 * func(4) # 5 * 4 * func(3) # 5 * 4 * 3 * func(2) # 5 * 4 * 3 * 2 * func(1) # 5 * 4 * 3 * 2 * 1 if __name__ ==
凯茜的老爸
2018/09/11
7910
PowerBI DAX 计算阶乘的方法
非常碰巧,在最近几个项目中都遇见计算阶乘的情况,主要是计算排列组合数的时候会用到阶乘。
BI佐罗
2019/09/23
1.5K0
PowerBI DAX 计算阶乘的方法
用ORCA计算旋轨耦合矩阵元
旋轨耦合的理论涉及相对论量子力学,此处仅以定性的形式粗略介绍相关背景。相对论效应是指进行电子结构计算时Dirac方程与Schrödinger方程这两个理论模型之间的差别。Dirac于1928年建立了电子运动的相对论方程——Dirac方程,但是Dirac本人却认为在化学问题中,价电子受内层电子的屏蔽,其运动速度比光速小很多,相对论效应很小。但在后来的研究中,人们逐渐认识到相对论效应的重要性。自旋-轨道耦合(spin-orbit coupling, SOC),简称旋轨耦合,是一种相对论效应,指电子的自旋和轨道运动之间的相互作用。在非相对论量子力学中,自旋态改变的跃迁是禁阻的;当考虑旋轨耦合时,这样的过程才能发生,比如系间窜越(intersystem crossing, ISC)、磷光发射等过程。在《用高斯计算磷光发射能》一文中我们提到,用TD-DFT直接计算T1和S0之间的跃迁,得到的振子强度始终为0,只有当考虑旋轨耦合后,振子强度才不为0。
用户7592569
2021/08/10
3.6K1
用ORCA计算旋轨耦合矩阵元
【蓝桥杯】BASIC-30 阶乘计算
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
喜欢ctrl的cxk
2019/11/13
8490
蓝桥杯 试题 基础练习 阶乘计算
#include<bits/stdc++.h> using namespace std; const int maxn = 50000; int f[maxn];//用于存放得到阶乘后每一个数字。 int main(){ int i,j,n; while(~scanf("%d",&n)){ memset(f,0,sizeof(f)); f[0]=1; //小于2的阶乘都为1 for(i=2;i
杨鹏伟
2020/09/11
5580
现代C++之模板元编程(今天写个If与While)
今天就放轻松,有可能代码写的看的很晦涩,自己多敲几遍即可,下面来进入正文,如何使用模板元编程实现IF与WHILE。
公众号guangcity
2020/02/13
1K0

相似问题

如何保存数据而不触发事件

34

Netsuite在保存付款时不触发重复号码屏幕后的用户事件脚本

214

等待而不阻塞事件触发

23

脚本标记中的onload事件不触发

21

.Net用户控件不触发事件

10
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文