Design program
唯一能够显著影响行为的学习是自我发现和自我学习
(数据抽象,过程抽象)
宏定义:将变量使用字符串来表达
问题栏:set!和define在开始定义的时候的区别,迭代(尾递归)和普通递归的效率问题?
基本程序设计步骤
(1)问题分析和数据定义
问题分析:
数据定义:
对于工资结构体而言,(define-struct wage (name money))
对于时间结构体而言,(define-struct time (name hour))
(2)合约,用途说明与结果描述,函数头部
(3)例子
(4)函数模板
(5)函数定义
(6)测试
参考程序如下:
;;问题分析: ;;数据定义: ;;定义工资结构体 (define-struct money (name wage)) ;;定义工作时间结构体 (define-struct times (name hour)) ;;工资结构体的赋值 (define Tony0 (make-money 'Tony 29)) (define Jerry0 (make-money 'Jerry 10)) ;;工作时间的赋值 (define Tony1 (make-times 'Tony 10)) (define Jerry1 (make-times 'Jerry 10)) ;;员工单位时间的价格的抽象 (define m (cons Tony0 (cons Jerry0 empty))) ;;员工工作时间的抽象 (define w (cons Tony1 (cons Jerry1 empty))) ;;函数合约:hour-wage:list of struct list of struct -> list of struct ;;函数功能:计算工资 ;;函数头部:(define (hour-wage hour wage)...) ;;例子1:(Tony 29) (Tony 10) ->(Tony 100) ;;例子2:empty ->empty (define (hour-wage hour wage) (cond [(empty? hour) empty] [else (cons (calculate(first hour) (first wage))(hour-wage (rest hour) (rest wage))) ])) ;;辅助函数 ;;函数功能: (define (calculate hour wage) (* (times-hour hour)(money-wage wage))) ;;测试 (hour-wage w m)
有多少种数据类型 ?
字符(symbol),真值(boolean),数字(number),结构体(struct)
结构体
什么是结构体?
物品 | 价格 | 图片 |
飞机
|
12 | 飞机.gif |
什么时候需要用到结构体?
某个对象的描述需要若干条信息(待测试)
譬如:库存清单里面的单行(一行)(待测试)
表
什么是表?
物品 |
飞机 |
洋娃娃 |
数表什么时候使用?(刚才完成到哪一个部分了(混合数据的抽象))
包含结构体的表
什么是包含结构体的表?
物品 | 价格 | 图片 |
飞机 | 133 | 飞机.gif |
洋娃娃 | 32 | 妹子。gif |
结构体定义:(define-struct list (something,price,picture))。
表结构体定义:(cons list empty),其中list是结构体。譬如:(cons (make-list 飞机 12 飞机.gif) (cons (make-list 洋娃娃 32 妹子.gif) empty))
在C语言中,是二维数组。
什么时候用到包含结构体的表?
如库存清单?(待测试)
包含结构体的结构体
树,家族图谱
包含表的表
1.递归
设计算法的步骤或者说核心
什么是平凡可解条件?
平凡可解条件是什么?
如何生成新的问题?
如何将解联系起来
终止论证(什么是终止论证,就是程序在什么时候结束)
1.1线性递归
什么是线性递归:在计算的时候,是从下(下指的是先算8+9)往上(上指的是最后算1+?(?指的是从后往前算出来的和))的计算。
譬如:1+2+3+4+5+6+7+8+9=1+(2+(3+(4+(5+(6+(7+(8+9)))))))
题目一:计算数a 到b的和
;;函数功能:计算a到b各个整数的和(线性递归) (define (sum-integers a b) (cond [(> a b) 0] [else (+ a (sum-integers (+ a 1) b))])) (sum-integers 3 7)
终止论证:
选取判断终止的变量: 累加器a 和展开表达式
累加器a 展开表达式
3 3+(...)
4 3+(4+(...))
由此类推,当a等于7的时候,表达式为3+(4+(5+(6+(7+(...)))))
要使得3+(4+(5+(6+(7+(...)))))=3+4+5+6+7+0成立,那么就得使得(...)=0,而这个时候a=8
以此其终止条件为a=8(或者说a=b+1),其结果为0
题目二:计算数a到b的各个整数的立方和
;;函数功能:计算a到b各个整数的立方和 (define (sum-cubes a b) (cond [(> a b) 0] [else (+ (* a a a) (sum-cubes (+ a 1) b))])) (sum-cubes 3 7)
平凡可解是什么:
平凡可解其对应的解:
如何生成比原问题更容易的:
如何将原问题和平凡解联系:采用加号
这里的原问题是f(m,n)=3的立方+...+7的立方
那么如何联系(归纳法)
3的立方+...+7的立方=3的立方+(4的立方+...+7的立方)
4的立方+...+7的立方=4的立方+(5的立方+...+7的立方)
与下面题目二的相对比,我们发现,他们也可以表示成
4*...*1=4*(3*...*1)
3*...*1=3*(2*...*1)
而我们却采取了下面这些数学符号来表示,如n!等。
那么也可以将其表示成
f(m)=g(m)+f(m-1)我们是出于什么目的使用统一的符号来表示不同的公式,我猜测是一种哲学思想----形而上学(待定,大概就是认为存在不变,本质性的东西)
接下来我们使用统一的符号来题目三的规律:
f(3,7)=g(3)+f(4,7)
f(4,7)=g(4)+f(5,7)
f(5,7)=g(3)+f(6,7)
由此我们可以推测是这么一种规律:f(m,n)=g(m)+f(m+1,n)
最后,我们对比程序以及上面的规律可以发现,程序中的第二个条件语句实际上是上面的规律(f(m,n)=g(m)+f(m+1,n)).(so crazy)
另外,我们还有一个问题就是,我们怎么确定n的选取?也就是换句话来说,我们这里存在多种可能性,譬如在这个题目当中
3的立方+...+7的立方=3的立方+(4的立方+...+7的立方)
4的立方+...+7的立方=4的立方+(5的立方+...+7的立方)
我们可以选取n表示最后一个数7,那么公式就会这样:
f(7)=g(..)+f(7)
f(7)=g(..)+f(7)
...
从中我们可以看出,选取7不能描述变化,同理对于第一条公式:3的立方+...+7的立方=3的立方+(4的立方+...+7的立方)来说,4,5,6都不行,那么只能选取3.
因此可以表示为f(n)=g(n)+f(n-1)
还有一个问题就是为什么采取f(m,n)=g(m)+f(m+1,n),而不是f(m)=g(m)+f(m+1),其实我们也可以使用这个编写程序,如下:
其中的else的子表达式就是f(m)=g(m)+f(m+1)
(define (sum-integers a b) (local( (define(s-i a) (cond [(> a b) 0] [else (+ a (s-i (+ a 1)))]))) (s-i a))) (sum-integers 2 7)
终止论证:
观察什么数据才能 判断程序是否应该结束?累加器,表达式的展开程度(对于递归来说),迭代的值(对于迭代来说)
譬如(在这一道题目当中):
累加器是 a,展开表达式是f(m)=g(m)+f(m+1)
那么:
a 表达式
2 2+(...)
3 2+(3+(...))
4 2+(3+(4+(...)))
如此类推,当a=7的时候,表达式的展开是2+(3+(4+(5+(6+(7+(...)))))
要符合2+3+4+5+6+7,那么就要求(...)为0,那么递归终结条件是a=8的时候,其数值为0
题目三:计算n的阶乘
;;函数功能:计算阶乘 (define (f! n) (cond [(= n 0) 1] [else (* n (f! (- n 1)))]))
(f! 4)
什么是平凡可解:n=1的时候
平凡可解对应的解:当n=1的时候,其对应的解是1.
如何生成比原问题更容易的解:和下面一样
如何将原问题和平凡可解联系起来:
这里的原问题是:求4的阶乘(4!)
那么如何联系:归纳法
4!=4*3!
3!=3*2!
2!=2*1!
如此类推,我们猜想n!=n*(n-1)!
证明:不证明(哈哈)·
题目四:对给定工资的处理
;;函数功能:给定工资单,计算工资 ;;函数合约:list of nummber -> list of number ;;函数头部:(define (salary list0)...) ;;终止条件:empty (define (salary list0) (cond [(empty? list0) empty] [else (cons (wages (first list0)) (salary (rest list0)))])) ;;计算工资 (define (wages num) (* num 12)) (salary '(12 45 56 78 89))
1.2 线性迭代
题目一:计算给定范围各点的乘积(递归版本,从1开始)
(define (mix0 i j) (local( (define (m a b) (cond [(> a b) 1] [else (* a (m (+ 1 a) b))]))) (m i j)))
(mix0 2 7)
累加器记住什么:
如何确定累加器:
累加器的初始值:
题目二:计算a到b各个整数的立方和(迭代计算)-自下而上
(define (sum-cubes-iter0 i j)
(local(
(define (s-c-i a b)
(cond
[(> a j) b]
[else (s-c-i (+ a 1) (+ (* a a a) b))])))
(s-c-i i 0)))
(sum-cubes-iter0 3 7)
列出其表达式:3³+4³+5³+6³+7³
累加器记住什么: 假定记住b=3³+4³,而下一个需要记住的是(3³+4³)+5³ , (b<-a³+b ,前者和后者的联系a<-a+1)
如何确定累加器: b<-a³+b a<-a+1
累加器的初始值: a=5,b=3³+4³
结束论证:
a b
5 3³+4³
6 3³+4³+5³
如此类推,当a=8的时候,才会有3³+4³+5³+6³+7³。
那么还有一个问题就是为什么累加器的初始值是a=3(或者说是 i),b=0呢?而不是a=5,b=3³+4³?
我认为有以下几点:一是为了抽象,什么意思呢?就是多个程序的累加器初始值统一,那么抽象的时候选取的变量就会少很多。
证据如下:题目二和题目三的累加初始值分别为a=5,b=3³+4³;a=2,b=3。那么我们进行抽象的时候,就需要使用新的词语来代替。
而如果统一使用a=i,b=0,那么就不用使用新的词语来替代。
题目三:计算a到b的各个整数之和
(define (mix0 i j) (local( (define (m a b) (cond [(> a b) 1] [else (* a (m (+ 1 a) b))]))) (m i j))) (mix0 2 7)
我们先从递归版本的计算a到b各个整数之和来看其中的流程,譬如(mixo 2 7)
2+(3..)
2+(3+(4..))
2+(3+(4+(5..)))
2+(3+(4+(5+(6..))))
2+(3+(4+(5+(6+7))))
2+(3+(4+(5+13)))
(3+(4+18))
(3+22)
25
而迭代版本的计算过程是这样的:
((((2+3)+4)+5)+6)+7
(((5+4)+5)+6)+7
(((9+5)+6)+7
(14+6)+7
20+7
27
有上述两条式子对比可知:递归版本是从左到右的拆分,再从右往左的计算;而迭代是从左往右的计算
累加器记住什么:记住前面求出的和,如2+3=5,5+4=9
如何确定累加器:一个是描述前面的数和后面的数的规律变化(- a 1),另外一个是记住求出来的和(+ b a)
累加器的初始值:a=2,b=3
根据对前面那一道题讨论结果(不利于抽象),那么我们一般不使用a=2,b=3进行抽象的。使用(用于表达前者和后者的关系)a=i,(累加器)b=0,累加器设计依旧。
结束论证:
a b
2 0
3 0+2
4 0+2+3
如此类推,那么当a=8的时候,那么b才等于=2+3+4+5+6+7+8
所以结束条件是a=b+1(即是本题的8)
1.3结构递归(又叫树形递归)
题目一:肥婆那些数列
题目二:计算list=(cons (list 1 2) (list 3 4))的深度
如何将原问题拆分成平凡可解
将原问题和平凡可解联系
f(list)=f((list 1 2 nil))+f((list 3 4 nil))
f((list 3 4))=f(3)+f((list nil))
因此可以归纳出,f(list)=f(car list)+f(cdr list)
f((list 1 2 nil))=f(1)+f((list 2 nil))
f((list 2 nil))=f(2)+f(nil)
因此,可以归纳出f(list)=g(car list)+f(cdr list),f代表的是函数名
其中的平凡可解是:
f(2)->1
f(1)->1
f(nil)->0
2.累加器(一次调用)
2.1什么时候使用累加器?
(1)使用递归,且递归的结果(指的是first xx rest xx)需要使用到辅助函数,可以考虑使用累加器
譬如:a.反转列表
b.计算前几个数的和
(2)正在处理的函数是生成递归
传教士和食人族(还没看)。。
2.2累加器要记忆什么
对于结构递归来说,是前几个数(所递归到得数值)的和
对于生成递归来说,是起几节点(即列表,结构体之类)
2.3如何设计累加器?
(1)累加器的初始值是什么? 如何将递归值(first rest之类)和累加值(acc)组合起来
递归什么?递归的结果是什么?
如下图例子所示:?(acc)+1(first alon)=1(acc)
这里递归的结果是1,那么?就是0,也就是说acc是0
总结:初始值:数:0 列表:empty(没确定例子)
组合:数值 + 列表:cons
具体的例子如下:
;;数据定义 (define alon0 (cons 1 (cons 2(cons 3 empty)))) ;;函数功能:计算1+2+3的和 ;;函数合约:list of number -> number ;;函数头部:(define (sum alon1)...) ;;例子1:1的时候,1+0=1 ;;例子2:2的时候,1+2=3 ;;例子3:empty的时候,总和(((0+1)+2)+3)=6 ;;什么时候需要用到累加器;和以前的知识有关(这里的知识指的是0 1 2) ;;累加器的用途:记住需要记住的知识是前面的数之和 ;;如何记住这些知识(如何设计累加器)考虑累加器第一个值是什么以及递归的结果, ;;当我输入无的时候,递归的结果应为1,?+1(first alon)=1(acc),所以累加器的第一个值为0 ;;当我输入2的时候,递归的结果应为3,1(acc)+2(first alon)=3(acc) (define (sum alon1) (local((define (sum0 alon acc) (cond [(empty? alon) acc] [else (sum0 (rest alon) (+ (first alon) acc))]))) (sum0 alon1 0))) (sum alon0)
3.设计有记忆的函数(多次调用)
什么是记忆函数,状态变量
(1)程序向用户提供不止一种服务,每种服务对应一个函数。譬如:增加电话号码和搜索电话号码
(2)程序向用户提供一种服务,并且在每一次调用的时候,都会出现不同的效果。譬如:红绿灯的程序,每次调用f都会改变current-color的颜色
;;数据定义 ;;状态分析 ;;函数功能:红-黄-绿 ;;函数合约 f:symbol ->void ;;函数头部:(define (f color)...) ;;例子1:红灯->黄灯 ;;例子2:黄灯->绿灯 ;;例子3:绿灯->红灯 (define current-color 'red) (define (f color) (cond [(symbol=? 'red color) (set! current-color 'yellow)] [(symbol=? 'yellow color) (set! current-color 'green)] [(symbol=? 'green color) (set! current-color 'red)])) (begin(f current-color) current-color (f current-color) current-color)
在什么情况下使用记忆?
如何使用?
(1)初始化状态变量,改变状态变量
累加器和记忆函数的区别
譬如:
使用累加器或记忆函数 定义为(define (next x y)...)
当时用累加器的时候,只调用一次,如 (next 2 3)
当使用记忆函数的时候,调用多次,如 (begin (next 4 6) (next 7 8))
辅助函数的设计
对数据的处理
4.抽象函数设计
好处:如果我们出现一个逻辑错误,所有使用该函数的东西都会被修正
大致说来,程序的维护包括:修改程序,使之能在以前未测试过的情形下正常运行;扩展程序,使之能处理新的或者以前未预见到的问题;将某些信息表达修改成数据(例如历法日期)。如果我们把所有的东西都放在一个地方,修改程序时就只要修改一个函数,而不是四个或五个相似的程序;扩展程序时也只要扩展一个函数,无须涉及相关的函数;而改变数据表示法意味着改变一个一般化的数据遍历函数,而不必修改所有源与同一模板的函数。
4.1什么时候可以抽象:
值的抽象(值包括 符号,字符串,数;函数也是值(待测试))
非值的抽象(警告): 如果要抽象东西的不是值,诀窍需要有本质的修改。
4.2怎么进行抽象
比较:如果发现两个函数的定义(除了少数地方以外)基本相同,就比较它们,标记出不同点。如果不同之处只是值,就可以对这两个函数进行抽象。
如果比较他们中间的不同点,比价处不同点(函数抽象)
抽象:接下来,我们用同样的名称替换每处不同点,并把这些新的名称加入到参数表中。
接下来,使用新的变量代替没出不同点,并把这些不同的参数加到参数列表中,而相同的参数新建一个新的函数(local(define xxx)),其参数为原来那些不变的参数。
测试:用新的抽象函数来定义原来的抽象函数。
合约:首先比较抽象前函数的合约,通过逐一替换对应位置上的不同类型,逐渐地得到一般化的合约。
当长度很长的数据的时候,可以采用抽象。
如结构体的创建(define Tony0 (make-money 'Tony 20))
如
当频繁使用的时候,可以采取抽象。
5.使用local
5.1在什么时候使用local?
当开发一个函数需要辅助函数的时候,我们可以把这两个函数编写到local当中
6.状态变量的封装
如何封装状态变量(又可以喊做闭包,对象)
;;函数功能:查找电话号码和新建电话号码 ;;函数合约: symbol -> number /void ;;函数头部:(define (phone sign)...) (define (phone title) (local( ;;定义电话本结构体 (define-struct pb (names nums));;为什么要写出来 ;;新建 (define pblist empty);;为什么要写出来 ;;函数功能:新建电话本 ;;函数合约:symbol number -> void ;;函数头部:(define (new-phonebook name num)...) ;;例子 (define (new-phonebook name num) (set! pblist (cons (make-pb name num) pblist))) ;;函数功能:查找电话号码 ;;函数合约:symbol -> number ;;函数头部:(define (lookup name)...) ;;例子:'(('jerry 123) ('kolol 456) empty) ;;empty -> empty ;;('kolol 456) ->456 ;;('jerry 123 )->下一个 (define (lookup sname pblist) (cond [(empty? pblist) empty] [(symbol=? (pb-names (first pblist)) sname) (pb-nums (first pblist))] [else (lookup sname (rest pblist))])) ;;函数功能:选择是查找电话号码·还是新建电话本 ;;函数合约:symbol -> void / number ;;函数头部:(define (select flag)...) (define (select flag) (cond [(symbol=? flag 'lookup) (lambda (name) (lookup name pblist))] [(symbol=? flag 'new-phonebook) new-phonebook]));;这里应该= =@ )select)) ;;定义一个公司的电话本 (define p1 (phone 'gongsi)) ;;定义一个其他的电话本 (define p2 (phone 'other)) ;;测试 (begin ((p1 'new-phonebook) 'kolol 456) ((p1 'new-phonebook) 'jerry 123) ((p1 'lookup) 'jerry)) ;;测试 (begin ((p2 'new-phonebook) 'kool 456) ((p2 'new-phonebook) 'jerr 12345) ((p2 'lookup) 'jerr))
什么时候需要用到多个状态变量
当需要管理多个电话本的时候,当需要管理多个红绿灯的时候
当编写多个红绿灯的时候(代码等会再贴上去)
7.循环结构体的收集(因果关系)
8.向量的应用(结构,生成递归)
====================================================================================================
sheme疑点的简单记录
;;将数值取出来, (define a '(1 2 3 4 5 6 7)) (define (num a) ( cond [(empty? a) empty] [else (begin (num (rest a)) (first a))])) ;;begin也是个表达式 ;;(begin (num '(2 3 4 5 6 7)) 2)) ;;(begin (begin (num '(3 4 5 6 7) 3) 2)) ;;由此类推,并且由于他是应用序求值,所以我们可以知道最后的结果只会是2 (num a)
未知:函数的注释,命名规范