用python和HY(lisp)代码为例学习什么是递归?

什么是递归?

看ANSI Common Lisp手册,里面提到递归第二章:欢迎来到 Lisp — ANSI Common Lisp 中文版,说:不要把递归看作一个普通函数来理解,因为普通函数经常被当成一个“机器”,原料从入口进,从出口出,但是对递归就有点难理解,因为这个机器又调用了自己.....所以要把递归想像成一个处理过程,不管是“机器”还是函数,它都只是针对的输入物料或信息的一种处理过程。

好吧,我刚看到原话的时候有种醍醐灌顶的感觉,但是请原谅我贫瘠的语言无法把它准确描述出来。我可以举个例子,比如查字典,它的处理过程就是“先按照某种规则翻到某一页,在这一页里面查看是否有要查找的‘字’,如果有,查找结束,如果没有,那就继续这个处理过程” 。当然这个翻页规则可以有多种,比如从前向后翻,比如从后向前翻,比如每次选取页数的1/2 来说翻。

我将使用python和HY(lisp)源代码的方式来辅助自己的理解,也希望能对大家有所帮助。

ps,不要有负担,其实递归真的很简单,我每隔一段时间就会弄明白一次^-^。

设定任务查字典

结合对“处理过程”的理解,我们设定了一个简化的查字典的任务,即给出一个字符(也可以是字符串)和一个包含字符(串)的列表,从列表中找出匹配项,并把匹配项后面的元素一起打出来。

还可以增加倒查模式,即查到后把匹配项前面的元素一起打出来。

比如给出x = "c" ; ListB = ["a", "b", "c", "d"] ,那么从左边查找的返回值是["c", "d"],从右边查找的返回值是 ["a", "b", "c"]

编码实现查字符串

首先学习一下HY

HY的语法

不要有负担,HY语言不会也没有关系,看完有个印象就行。毕竟“处理过程”是LISP书里提到的,我们用HY lisp语言跑通例子会更优仪式感^-^。

HyPython

(setv foobar (+ 2 2))

(setv [tim eric] ["jim" "derrick"])

(setv alpha "a" beta "b")

foobar = 2 + 2

tim, eric = 'jim', 'derrick'

alpha = 'a'; beta = 'b'

(sorted "abcBC"

  :key (fn [x] (.lower x)))

sorted("abcBC",

    key = lambda x: x.lower())

(defn test [a b [c "x"] #* d]

  [a b c d])

def test(a, b, c="x", *d):

    return [a, b, c, d]

(with [o (open "file.txt" "rt")]

  (setv buffer [])

  (while (< (len buffer) 10)

    (.append buffer (next o))))

with open('file.txt', 'rt') as o:

    buffer = []

    while len(buffer) < 10: 

        buffer.append(next(o))

(lfor

  x (range 3)

  y (range 3)

  :if (= (+ x y) 3) (* x y))

[x * y

    for x in range(3)

    for y in range(3)

    if x + y == 3]

(defmacro do-while [test #* body]

  `(do

  ~@body

  (while ~test

    ~@body)))

(setv x 0)

(do-while x

  (print "Printed once."))

x = 0

print("Printed once.")

while x:

    print("Printed once.")

HY的安装

在python环境下,直接使用pip安装即可

pip install hy

安装完成后,命令行模式下直接键入“hy”即可进入hy的交互界面

hy
Hy 0.29.0 using CPython(main) 3.10.13 on FreeBSD
=> (print "hello world")
hello world

pyhton实现

任务需求:给出x = "c" ; ListB = ["a", "b", "c", "d"] ,那么从左边查找的返回值是["c", "d"],从右边查找的返回值是 ["a", "b", "c"]

python代码如下:

# 学习递归 从右边查找匹配项
def thirdright(x, ListA):
    if not ListA :
        return None
    else:
        if x == ListA[-1]:
            return ListA
        else:
            return thirdright(x, ListA[:-1])

# 学习递归 从左边查找匹配项
def thirdleft(x, ListA):
    if not ListA :
        return None
    else:
        if x == ListA[0]:
            return ListA
        else:
            return thirdleft(x, ListA[1:])

运行测试

x = "c" ; ListB = ["a", "b", "c", "d"]
y = thirdleft(x, ListB)
z = thirdright(x, ListB)
print(y,z)

输出结果:

['c', 'd'] ['a', 'b', 'c']

HY lisp代码实现

任务需求:给出x = "c" ; ListB = ["a", "b", "c", "d"] ,那么从左边查找的返回值是["c", "d"],从右边查找的返回值是 ["a", "b", "c"]

当然在HY里,赋值语句是(setv x "c") (setv ListB ["a" "b" "c" "d"])

从左向右查找HY代码:

(defn searchleft [x ListA] 
(if (= ListA [])
  None
  (if (=  (get ListA 0) x)
    ListA 
    (searchleft x  (cut ListA 1 100)))))

(setv x "c") (setv ListB ["a" "b" "c" "d"])
(searchleft x ListB)

输出

["c" "d"]
 

从左向右查找HY代码:

(defn searchright [x ListA] 
(if (= ListA [])
  None
  (if (=  (get ListA -1) x)
    ListA 
    (searchright x  (cut ListA 0 -1)))))

(setv x "c") (setv ListB ["a" "b" "c" "d"])
(searchright x ListB)

输出

=> (searchright x ListB)
["a" "b" "c"]

HY lisp代码确实完成了功能需求,除了有太多右括号“)))))”,程序代码整体看着还是挺清爽的。

HY里面没有常规lisp语言里的cdr和car两条指令,所以这里用了get 和cut两个函数,感觉不如cdr和car丝滑。

递归深度限制和尾递归

python不支持尾递归,且有递归深度限制

def recursion(n):
    if n==1:
        return n
    else:
        return n+recursion(n-1)  

最大递归深度1000,所以数大点就报错了

recursion(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in recursion
  File "<stdin>", line 5, in recursion
  File "<stdin>", line 5, in recursion
  [Previous line repeated 995 more times]
  File "<stdin>", line 2, in recursion
RecursionError: maximum recursion depth exceeded in comparison

 HY是基于python的,所以也有最大递归深度限制

(defn recursion [x]  
  (if (= x 1)  
    1  
    (+ x (recursion (- x 1)))))

(recursion 1000)

报错:RecursionError: maximum recursion depth exceeded in comparison

hy的尾递归代码没调通。
 

common Lisp的代码

(defun recursion (x)
  (if (= x 1)  
    1  
    (+ x (recursion (- x 1)))))

3000的时候没问题,到3400的也是报错

(recursion 3400)

*** - Lisp stack overflow. RESET
 

sbcl尾递归

目前只有sbcl支持尾递归

(defun recursion-tail (x &optional (sum 0))  
  (if (= x 1)  
      (+ sum 1)  
      (recursion-tail (- x 1) (+ sum x))))  
  
; 调用函数  
(recursion-tail 1000)

 在sbcl的尾递归里,可以轻松到五百万深度

* (recursion-tail 5000000)
12500002500000

证明尾递归优化确实了不起!

总结

递归就是行为规则,是一种处理过程,递归也是最符合人类原生行为的编程方法,所以即使很复杂的问题,也比较容易用递归来实现算法。比如汉诺塔,使用递归代码非常简洁,使用循环则代码复杂很多。

关于递归的资源消耗,确实要比普通函数多,不过很多编译器专门对递归进行了优化,许多Common Lisp 编译器(比如sbcl)都可以把尾递归转化成循环函数,这样就没有额外的资源消耗了。

再回过头来看看ANSI Common Lisp关于递归的描述,加强一下记忆

起初,许多人觉得递归函数很难理解。大部分的理解难处,来自于对函数使用了错误的比喻。人们倾向于把函数理解为某种机器。原物料像实参一样抵达;某些工作委派给其它函数;最后组装起来的成品,被作为返回值运送出去。如果我们用这种比喻来理解函数,那递归就自相矛盾了。机器怎可以把工作委派给自己?它已经在忙碌中了。

较好的比喻是,把函数想成一个处理的过程。在过程里,递归是在自然不过的事情了。日常生活中我们经常看到递归的过程。举例来说,假设一个历史学家,对欧洲历史上的人口变化感兴趣。研究文献的过程很可能是:

  1. 取得一个文献的复本
  2. 寻找关于人口变化的资讯
  3. 如果这份文献提到其它可能有用的文献,研究它们。

过程是很容易理解的,而且它是递归的,因为第三个步骤可能带出一个或多个同样的过程。

所以,别把 our-member 想成是一种测试某个东西是否为列表成员的机器。而是把它想成是,决定某个东西是否为列表成员的规则。如果我们从这个角度来考虑函数,那么递归的矛盾就不复存在了。

调试 

hy里没有切片

=> ["a" "b"][0]
[0]
=> ["a" "b"][0:1]
Traceback (most recent call last):
  File "stdin-ff769649939a083bf75b8d8e4be71e7a4f33d180", line 1, in <module>
    ["a" "b"][0:1]
NameError: name 'hyx_0XcolonX1' is not defined
可以用cut代替

=> (cut [1 2] 0)
[]
=> (cut [1 2 3] 1)
[1]
=> (cut [1 2 3] 2)
[1 2]
=> (cut [1 2 3] -1)
[1 2]
如果读取某个元素,更适合的是get

=> ListB
["a" "b" "c" "d"]
=> (get ListB 0)
"a"
=> (get ListB 1)
"b"

hy源码:GitHub - hylang/hy: A dialect of Lisp that's embedded in Python

手册:Contents — Hy 0.29.0 manual 

指引:Tutorial — Hy 0.29.0 manual 

代码报错parse error for pattern macro 'if': got unexpected token: 

...   (+ x (recursion (- x 1)))))
Traceback (most recent call last):
  File "stdin-9598c437ea17b110b2200908cd5c44900ea2edef", line 4
    (+ x (recursion (- x 1)))))
    ^
hy.errors.HySyntaxError: parse error for pattern macro 'if': got unexpected token: hy.models.Expression([
  hy.models.Symbol('+'),
  hy.models.Symbol('x'),
  hy.models.Expression([
    hy.models.Symbol('recursion'),
    hy.models.Expression([
      hy.models.Symbol('-'),
      hy.models.Symbol('x'),
      hy.models.Integer(1)])])]), expected: end of macro call

python递归报错

>>> recursion(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in recursion
  File "<stdin>", line 5, in recursion
  File "<stdin>", line 5, in recursion
  [Previous line repeated 995 more times]
  File "<stdin>", line 2, in recursion
RecursionError: maximum recursion depth exceeded in comparison

python有最大递归深度限制,最大1000

import sys

sys.getrecursionlimit()
1000
可以使用sys.setrecursionlimit进行设置。

hy尾递归代码报错

 (defn recursion-tail [x &optional [sum 0]]  
...     (if (= x 1)  
...         (+ sum 1)  
...         (recursion-tail (- x 1) (+ sum x))))  
=> (recursion-tail 1000)
Traceback (most recent call last):
  File "stdin-db38a56499b0c167c6ac4f20a4f89229b594a496", line 1, in <module>
    (recursion-tail 1000)
TypeError: recursion_tail() missing 1 required positional argument: 'hyx_XampersandXoptional'

先搁置

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值