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)

 

 

未知:函数的注释,命名规范

posted @ 2016-10-03 19:06  河边青青草  阅读(374)  评论(0编辑  收藏  举报