《how to design programs》12章函数复合

时间:2023-03-09 19:40:45
《how to design programs》12章函数复合

我们写代码时要学会适应辅助函数。作者提出了一个问题,如何对一个表排序。排序函数读取一个表,产生另一个表。排序函数的合约和用途如下:

(sort empty)
;; expected value:
empty
(sort (cons 1297.04 (cons 20000.00 (cons -505.25 empty))))
;; expected value:
(cons 20000.00 (cons 1297.04 (cons -505.25 empty))) 完整程序:(实际上就是插入排序)
#lang racket
;
;;sort:list-of-numbers->list-of-numbers
;;相当于插入排序
(define (sort alon)
(cond
[(empty? alon) empty]
[else (insert (car alon) (sort (cdr alon)) )]
)) ;insert:number list-of-numbers->list-of-numbers
;插入n到alon(降序表)中
(define (insert n alon)
(cond
[(empty? alon) (cons n empty)]
[else (cond
[(>= n (car alon)) (cons n alon)]
[else (cons (car alon) (insert n (cdr alon)))]
)]
)) (sort empty)
(sort (cons (cons (cons (cons (cons empty))))))

程序写起来还是有点困难的。

习题12.2.2.   Here is the function search:

;; search : number list-of-numbers  ->  boolean
(define (search n alon)
(cond
[(empty? alon) false]
[else (or (= (first alon) n) (search n (rest alon)))]))

It determines whether some number occurs in a list of numbers. The function may have to traverse the entire list to find out that the number of interest isn't contained in the list.

Develop the function search-sorted, which determines whether a number occurs in a sorted list of numbers. The function must take advantage of the fact that the list is sorted.

(函数search-sorted一般称为线性查找)

;; search-sorted : number list-of-numbers -> boolean
;; to determine if n is is alon
(define (search-sorted n alon)
(cond
[(empty? alon) #f]
(else (cond
[(= n (car alon)) #t]
[(< n (car alon)) #f]
[(> n (car alon)) (search-sorted n (cdr alon))]
))))
;; EXAMPLES AS TESTS
(search-sorted empty) "should be" false (search-sorted (cons (cons (cons (cons empty)))))
"should be" true (search-sorted (cons (cons (cons empty))))
"should be" false

问题泛化与函数泛华(generalize problem generialize function)

考虑多边形的绘制,

Consider the problem of drawing a polygon, that is, a geometric shape with an arbitrary number of corners.37 A natural representation for a polygon is a list of posn structures:

list-of-posns is either

  1. the empty list, empty, or

  2. (cons p lop) where p is a posn structure and lop is a list of posns.

Each posn represents one corner of the polygon. For example,

(cons (make-posn 10 10)
(cons (make-posn 60 60)
(cons (make-posn 10 60)
empty)))

represents a triangle. The question is what empty means as a polygon. The answer is that empty does not represent a polygon and therefore shouldn't be included in the class of polygon representations. A polygon should always have at least one corner, and the lists that represent polygons should always contain at least one posn. This suggest the following data definition:

(现在的问题是empty代表什么多边形,答案是empty并不代表多边形,所有empty类型并不是多边形,多边形至少应包含一个顶点,即表示多边形的表至少要包含一个posn结构体,隐藏,我们有如下数据定义:)

polygon is either

  1. (cons p empty) where p is a posn, or

  2. (cons p lop) where p is a posn structure and lop is a polygon.

In short, a discussion of how the chosen set of data (lists of posns) represents the intended information (geometric polygons) reveals that our choice was inadequate. Revising the data definition brings us closer to our intentions and makes it easier to design the program.

简而言之,使用有posn组成的表来表示多边形是不恰当的,数据定义的修改是我们更接近模板,编程更坚定。

Because our drawing primitives always produce true (if anything), it is natural to suggest the following contract and purpose statement:

因为绘图操作的返回值总是true,自然我们可以得出如下合约
;; draw-polygon : polygon  ->  true
;; to draw the polygon specified by a-poly
(define (draw-polygon a-poly) ...)

In other words, the function draws the lines between the corners and, if all primitive drawing steps work out, it produces true. For example, the above list of posns should produce a triangle.

这个函数画出顶点之间的连线,在所有的连线画出后,函数返回true。虽然现在的定义与常用表的定义并不相同,模板仍然和表处理

函数的模板类似:

;; draw-polygon : polygon  ->  true
;; to draw the polygon specified by a-poly
(define (draw-polygon a-poly)
(cond
[(empty? (rest a-poly)) ... (first a-poly) ...]
[else ... (first a-poly) ...
... (second a-poly) ...
... (draw-polygon (rest a-poly)) ...]))

完整代码:

(define (draw-polygon a-poly)
(cond
[(empty? (rest a-poly)) true]
[else (and (draw-solid-line (first a-poly) (second a-poly))
(draw-polygon (rest a-poly)))])) (draw-polygon (cons (make-posn )
(cons (make-posn )
(cons (make-posn )
empty)))
)

刚开始执行时总是报错:

evaluate (start <num> <num>) first。

google了一下没找到解决办法,自己摸索了下,在最前面加上

(start 100 100)

就可以了。后面的2个参数开始窗口的width和height)。

注:使用语言必须是《how to design programs》,其中的first,second和rest为how to design programs自己定义的.

(define v (cons 1 (cons  2 (cons 3 empty))))

> (first v)
1
> (second v)
2
> (rest v)
(list 2 3)

不幸的是,若用上面的代码对函数进行三角形绘制测试,我们可以立即发现,这个函数并没有绘制出一个含有3条边的多边形,而是绘制出一条连接所有顶点的开发曲线:

《how to design programs》12章函数复合

用数学的语言说,我们得到的函数比所需要的更一般,我们刚才定义的函数应该叫connect-the-dots,而不是draw-polygon。

要从这个一般的函数得到所需的函数,必须把最后一个点和第一个点连接起来,有几种方法可以做到这一点,而所有方法都要用到我们刚才定义的函数。换句话说,我们在一般的函数之上,在定义一个辅助函数。

一种方法是,定义一个新函数,把多边形的第一个顶点添加到表的末端,然后用新生成的表来绘制图形;另一种办法是,把最后一个顶点添加到表的前端;还有一种办法是修改draw-polygon函数,使他能够把最后一个顶点和第一个顶点连接起来。这里讨论第二种办法,其他2中作为练习。

要把a-poly的最后一个元素加到它的前端,我们需要:

(cons (last a-poly) a-poly)

其中last是个辅助函数,它的作用是提取非空表的最后一个元素。事实上,定义了 last,就得到了draw-polygon的定义:

;; draw-polygon : polygon  ->  true
;; to draw the polygon specified by a-poly
(define (draw-polygon a-poly)
(connect-dots (cons (last a-poly) a-poly))) ;; connect-dots : polygon -> true
;; to draw connections between the dots of a-poly
(define (connect-dots a-poly)
(cond
[(empty? (rest a-poly)) true]
[else (and (draw-solid-line (first a-poly) (second a-poly) 'red)
(connect-dots (rest a-poly)))])) ;; last : polygon -> posn
;; to extract the last posn on a-poly
(define (last a-poly)
(cond
[(empty? (rest a-poly)) (first a-poly)]
[else (last (rest a-poly))]))

总的来说,draw-polygon的设计是我们想到了一个更一般的问题:连接表中各点。通过定义辅助函数,使用更一般的函数(泛化函数),我们解决了问题。

问题:程序读入一个单词,返回对字母重新排序产生的单词表。

一个表示单词的方式是使用符号表,表中的每一个元素代表一个字母:'a,'b...下面是单词的定义:

word is either

  1. empty, or

  2. (cons a w) where a is a symbol ('a'b...'z) and w is a word.

Problem Statement
#| ------------------------------------------------------------------------
Permutations Data Definitions:
A word is either
* empty
* (cons S W) where S is a (letter) symbol and W is a word. A list-of-words is either
* empty
* (cons W L) where W is a word and L is a list-of-words. Examples:
empty is a word
(list 'b) is a word
(list 'a 'b) is a word
(list 'b 'a) is a word empty is a list of words
(list (list 'a 'b)) is a list of words
(list (list 'a 'b) (list 'b 'a)) is a list of words arrangements : word -> (listof word) Purpose: compute all possible arrangements (permutations)
of all the letters in a a-word; the result is a list of
words Example:
empty has one arrangement empty, so the result should be
- (list empty)
(list 'a) has one arrangment: (list 'a), so the result
should be
- (list (list 'a))
(list 'a 'b) has two different arrangements:
- (list 'b 'a)
- (list 'a 'b)
(list 'a 'b 'c) has six different arrangements:
- (list 'a 'b 'c)
- (list 'a 'c 'b)
- (list 'b 'a 'c)
- (list 'b 'c 'a)
- (list 'c 'a 'b)
- (list 'c 'b 'a)
Note: we should treat all "letters" as distinct from each other.
|#
(define (arrangements a-word)
(cond
((empty? a-word) (list empty))
(else (insert-everywhere/all-words (first a-word)
(arrangements (rest a-word))))))
;; We need a "helper" that combines the permutations for the rest
;; of the word with the first letter: insert-everywhere/all-words does the job. #| ------------------------------------------------------------------------
insert-everywhere/all-words : letter list-of-words -> list-of-words
Purpose: insert a-letter into all words of lo-words
Example:
'a (list (list 'b)) produces (list (list 'a 'b) (list 'b 'a))
'a (list (list 'b) (list 'c)) produces
(list (list (list 'a 'b) (list 'b 'a))
(list (list 'a 'c) (list 'c 'a)))
|#
(define (insert-everywhere/all-words letter lo-words)
(cond
((empty? lo-words) empty)
(else (append (insert-everywhere letter (first lo-words))
(insert-everywhere/all-words letter (rest lo-words))))))
;; We need a "helper" that inserts a letter into a single word. #| ------------------------------------------------------------------------
insert-everywhere : letter word -> list-of-words Purpose: insert a-letter everywhere into word, beginning, end and
between all letters of the word Examples:
'a (list 'b) ==> (list (list 'a 'b) (list 'b 'a))
'a (list 'b 'c) ==> (list (list 'a 'b 'c)
(list 'b 'a 'c)
(list 'b 'c 'a))
|# (define (insert-everywhere a-letter a-word)
(cond
((empty? a-word) (list (list a-letter)))
(else (cons (cons a-letter a-word)
(add-at-beginning (first a-word)
(insert-everywhere a-letter (rest a-word)))))))
;; We need a helper that adds a letter to the beginning of a list of words. #| ------------------------------------------------------------------------
add-at-beginning : letter list-of-words -> list-of-words
Purpose: add a-letter to the beginning of every word in lo-words
Example:
'a (list (list 'b 'c) (list 'c 'b))) should produce
(list (list 'a 'b 'c) (list 'a 'c 'b)))
|#
(define (add-at-beginning a-letter lo-words)
(cond
((empty? lo-words) empty)
(else (cons (cons a-letter (first lo-words))
(add-at-beginning a-letter (rest lo-words)))))) #| ------------------------------------------------------------------------
Tests: (add-at-beginning 'a (list (list 'b 'c) (list 'c 'b)))
=
(list (list 'a 'b 'c) (list 'a 'c 'b)) (insert-everywhere 'a (list 'b))
=
(list (list 'a 'b) (list 'b 'a)) (insert-everywhere/all-words 'a (list (list 'b)))
=
(list (list 'a 'b) (list 'b 'a)) (insert-everywhere/all-words 'a (list (list 'b) (list 'c)))
=
(list (list 'a 'b) (list 'b 'a)
(list 'a 'c) (list 'c 'a))
|# (arrangements empty)
= (list empty) (arrangements (list 'a))
= (list (list 'a)) (arrangements (list 'a 'b))
= (list (list 'a 'b)
(list 'b 'a)) (arrangements (list 'a 'b 'c))
= (list (list 'a 'b 'c)
(list 'a 'c 'b)
(list 'b 'a 'c)
(list 'b 'c 'a)
(list 'c 'a 'b)
(list 'c 'b 'a))

可以看到,用sheme写一个排列还是需要仔细思考的。