详解Clojure的递归(下)——相互递归和trampoline
详解clojure递归(下)
这篇blog拖到现在才写,如果再不写,估计就只有上篇没有下篇了,趁周末写一下。
上篇提到了Clojure仅支持有限的TCO,不支持间接的TCO,但是有一类特殊的尾递归clojure是支持,这就是相互递归。且看一个例子,定义两个函数用于判断奇数偶数:
(defn my-odd? [n]
(if (= n 0)
false
(my-even? (dec n))))
(defn my-even? [n]
(if (= n 0)
true
(my-odd? (dec n))))
这里使用declare做前向声明,不然在定义my-odd?的时候my-even?还没有定义,导致出错。可以看到,my-odd?调用了my-even?,而my-even?调用了my-odd?,这是一个相互递归的定义:如果n为0,那肯定不是奇数,否则就判断n-1是不是偶数,反之亦然。
这个递归定义看起来巧妙,但是刚才已经提到clojure通过recur支持直接的TCO优化,这个相互递归在大数计算上会导致堆栈溢出:
java.lang.StackOverflowError (NO_SOURCE_FILE:0)
user=> (my-even? 10000)
java.lang.StackOverflowError (NO_SOURCE_FILE:0)
怎么解决呢?一个办法是转化成一个直接递归调用,定义一个parity函数,当偶数的时候返回0,当奇数的时候返回1:
(loop [n n par 0]
(if (= n 0)
par
(recur (dec n) (- 1 par)))))
user=> (parity 3)
1
user=> (parity 100)
0
user=> (parity 10000)
0
user=> (parity 10001)
1
parity是直接的尾递归,并且使用了recur优化,重新定义my-odd和my-even就很简单了:
(defn my-odd? [n] (= 1 (parity n)))
但是这样的方式终究不是特别优雅,相互递归的定义简洁优雅,有没有什么办法保持这个优雅的定义并且避免堆栈溢出呢?答案是trampoline,翻译过来是弹簧床,查看trampoline函数文档:
-------------------------
clojure.core/trampoline
([f] [f & args])
trampoline can be used to convert algorithms requiring mutual
recursion without stack consumption. Calls f with supplied args, if
any. If f returns a fn, calls that fn with no arguments, and
continues to repeat, until the return value is not a fn, then
returns that non-fn value. Note that if you want to return a fn as a
final value, you must wrap it in some data structure and unpack it
after trampoline returns.
简单来说trampoline接收一个函数做参数并调用,如果结果为函数,那么就递归调用函数,如果不是,则返回结果,主要就是为了解决相互递归调用堆栈溢出的问题,查看trampoline的定义:
([f]
(let [ret (f)]
(if (fn? ret)
(recur ret)
ret)))
看到trampoline利用recur做了尾递归优化,原有的my-odd?和my-even?需要做一个小改动,就可以利用trampoline来做递归优化了:
(defn my-odd? [n]
(if (= n 0)
false
#(my-even? (dec n))))
(defn my-even? [n]
(if (= n 0)
true
#(my-odd? (dec n))))
唯一的改动就是函数的末尾行前面加了个#,将递归调用修改成返回一个匿名函数了,使用trampoline调用my-even?和my-odd?不会再堆栈溢出了:
false
user=> (trampoline my-even? 10000)
true
user=> (trampoline my-even? (* 1000 1000))
true
文章转自庄周梦蝶 ,原文发布时间 2010-08-22
详解Clojure的递归(下)——相互递归和trampoline相关推荐
- python命令提示符窗口在哪里_详解python命令提示符窗口下如何运行python脚本
以arcgispro的python脚本为例在arcgispro自带的python窗口下运行python脚本 需求: 将arcgispro的.aprx项目包中gdb的数据源路径更换为sde数据源路径. ...
- oracle通过dblink连接mysql配置详解(全Windows下)
oracle通过dblink连接mysql配置详解(全Windows下) 关于oracle通过dblink连接mysql,经过了两周的空闲时间研究学习,终于配置好了,真是不容易啊,仔细想想的话,其实也 ...
- 一篇文章带你详解 HTTP 协议(下)
文章目录,方便阅读: 一.概述(已讲) 二.HTTP 工作过程(已讲) 三.HTTP 协议基础(已讲) 四.HTTP 协议报文结构(已讲) 五.HTTP 报文首部之请求行.状态行(已讲) 六.HTTP ...
- Linux用户管理详解大结局(下)
Linux用户管理详解(下) 我们已经可以通过创建不同的用户来防止其他人使用自己的账号,之后每个账户对应一个单独的用户密码,构成了一个基本的用户管理思路.为了方便管理还可以使用组来设置相同属性的用户. ...
- Python choices()函数详解、random模块下的常用函数
random模块下的方法详解: 1.random.random() 随机生成一个[0,1)之间的浮点数. 2.random.randint(a,b) 随机生成[a,b]范围内一个整数. 3.rando ...
- 打靶归来 - 详解upload-labs靶场(下)
一.环境准备 ① - 靶场的搭建 下载地址:upload-labs upload-labs靶场曾有过一次更新,更新新添加了一道Pass-05,有一些以前的教程的题号与这篇教程不符合,请各位自行分辨 本 ...
- boos里的AHCI RAID_希洛克团本详解 DNF国服环境下Raid困难模式
随着9月底的临近,大部分玩家都在期待着本月底的金秋版本更新,而全新的金秋版本给大家带来的主要内容之一当然还是大家期待了很久的全新团本希洛克Raid.那么国服里的希洛克具体是什么样的呢?快跟小编一起走进 ...
- linux查找日期目录,详解Linux查找目录下的按时间过滤的文件
在维护项目中,有时会指定都一些条件进行过滤文件,并对该批文件进行操作:这时我们将使用shell命令进行操作:直接上代码 #!/bin/sh #BEGIN #`find ./ ! -name " ...
- linux依据时间过滤文件,详解Linux查找目录下的按时间过滤的文件
在维护项目中,有时会指定都一些条件进行过滤文件,并对该批文件进行操作:这时我们将使用shell命令进行操作:直接上代码 #!/bin/sh #BEGIN #`find ./ ! -name " ...
最新文章
- 特征值与特征向量 matlab数值解,用MATLAB和numpy求解特征值和特征向量,matlab,与
- gevent -1073740791
- 聊聊clean code
- Linux kernel 3.10内核源码分析--进程上下文切换
- ASP.NET Core gRPC 使用 Consul 服务注册发现
- ajax漏洞 console_在实战中可能碰到的几种ajax请求方法详解
- Dynamics CRM2013 Server2012R2下IFD部署遇到There is already a listener on IP endpoint的解决方法...
- python学习day05
- mysql中更改字符集为utf8mysql中文输入不了问题解决
- easyexcel多个sheet导入_Easypoi实现excel多sheet表导入导出功能
- Day21 linux安装RPM包
- 在内核中使用线程与skb队列发送数据
- 【教程】如何在C#中创建PDF417条码
- SWMM[Storm Water Management Model]模型代码编译调试环境设置
- Redis 模糊查询Key
- c++异常机制(转载)
- 数据库和 MIDP,第一部分:了解记录管理系统
- 威斯康星大学计算机科学教授,美国威斯康星大学周家振访问沈阳自动化所
- Flink教程(03)- Flink环境搭建
- 如何在Win10系统下的IntelliJ IDEA 2018.3.5下载与安装以及激活教程