我正在尝试编写一个常规的递归函数和一个尾递归函数,该函数接受四个参数并评估从ax^2 + b
到x
的{{1}}的总和q
}。
这是我到目前为止所做的并且无法正常工作,我可以获得一些有关我的代码有什么问题的信息吗?
r
答案 0 :(得分:3)
这是调用递归的正确方法:
(define (sumof a b q r)
(define (sum q) (+ (* (expt q 2) a) b))
(if (= q r)
0
(+ (sum q)
(sumof a b (+ q 1) r))))
您应该传递在每次迭代时更改的参数,这比从定义环境中捕获它更清晰。并注意sumof
函数必须如何调用自身进行迭代。作为旁注,您的函数不尾递归。正确的尾递归看起来像这样:
(define (sumof a b q r)
(define (sum x) (+ (* a x x) b))
(let loop ((q q) (acc 0))
(if (= q r)
acc
(loop (+ q 1) (+ (sum q) acc)))))
答案 1 :(得分:2)
编写代码很困难并不奇怪。虽然递归很有用,但这是引入它的最糟糕方式,因为使用递归来解决这个问题与任何对软件工程实践的倾向都不相符。
看看基本等式:
很明显,问题没有完全明确。是x < r
还是x <= r
?至少编写规范时假设可能导致off-by-one错误明确。
我的意思是我说的这个问题对声音软件工程有害。有递归的地方。这些地方正是递归使代码更清晰,更容易理解的地方。这是不其中一个案例。这是规范是迭代的情况,并且递归地编写实现增加了复杂性和不透明度。
如果我们接近规范:
#lang racket
(provide sumof)
(define/contract (sumof a b q r)
(number? number? number? number? . -> . number?)
(define/contract (sigma list-of-numbers f)
((listof number?) (number? . -> . number?) . -> . number?)
(foldl + 0 (map f list-of-numbers)))
;; range will return '() if r + 1 >= q
(define q<=x<=r (range q (add1 r)))
;; a and b are already in lexical scope
(define (ax^2+b x)
(+ (* a x x) b))
;; if the range is empty return zero
;; becuase zero is the identity of addition
(if (pair? q<=x<=r)
(sigma q<=x<=r ax^2+b)
0))
这使用高级操作符map
和foldl
,所有今天很酷的语言都认为是猫的喵喵叫。也许是因为它让我们写出像(sigma q<=x<=r ax^2+b)
这样的东西。
这是如此糟糕的原因是尾递归的计算机科学建立在算法和迭代描述的特定类递归描述之间的同构性之上。编程语言的想法是使我们的程序阅读和编写更容易。
虽然人们永远不会根据赋值来猜测#lang racket
包含一个构造,其目的只是为了明确表达尾递归的同构,并更清楚地编写尾递归算法。
#lang racket
(provide sumof)
(define/contract (sumof a b q r)
(number? number? number? number? . -> . number?)
;; a and b are already in lexical scope
(define (ax^2+b x)
(+ (* a x x) b))
(if (< q r)
(let loop ([x-range (range q (add1 r))]
[sigma 0])
(cond
[(null? x-range) sigma]
[else (loop (rest x-range)
(+ (ax^2+b (first x-range))
sigma))]))
0))
(let loop...
语法清楚地表明尾递归调用实际上是一个循环。