Lecture22
写一个名为power_set的函数, 它将生成包含输入集合所有子集的集合.
例:
> ( power_set '(1 2 3) )
( () (2) (3) (2 3) (1) (1 2) (1 3) (1 2 3)) // 注意: 是组合, 不是排列.
以上结果可以看成是由两部分组成:cons '(() (2) (3) (2 3)) '((1) (1 2) (1 3) (1 2 3))
另 外:
> (power_set '())
(())
定义:
(define (ps set)
( if (null?set) '(())
( append ( ps (cdr set ) ) // 1
( map ( lambda ( subset)
( cons (car set) subset ) ) // lambda中的函数用于map 操作时, 对列表中每个元素进行运算.
( ps ( cdr set ) ) ) ) ) ) // 2 本行产生map操作的列表
以上代码中, 1/2两处的set指向同一个集合对象 Scheme中通过变量名来向对象是唯一的方法.
//--------------------------------------------------------
写递归函数的技巧:
A. 最简值中抽出规则:1. 尾元素+ 剩余元素 2. 列出几个头元素的具体生成
B. 给出最简时的值:初值. 采用A中获得的递归规则进行叠加.
C. 写递归函数时往往是从给定的n开始,反向递减.
在Scheme语言中, 没有循环, 一般用递归函数来代替循环.
//--------------------------------------------------------
( define ( ps set )
( if ( null ? set) '(())
( let ( ( ps_rest ( ps ( cdr set ) ) ) ) // 使得只作一次cdr运算, 运算结果可以在let 范围内各处使用, 从而节省时间.
( append ps_rest
( map ( lambda ( subset)
( cons ( car set) subset ) )
ps_rest ) ) ) ) )
此定义与上定义等价,但运行效率高许多.
//--------------------------------------------------------
let 表达式:
( let ( ( x expv1)
( y expv2)
( z expv3) )
... )
实际上:
( let ( ( x ...)
( y ...) )
( a x y ) )
等价于:
(( lambda ( x y )
( a x y )) ... ...)
let 是换一种不同的方式将一些应用函数表示成一些有待评估的表达式.
//------------------------------------------------------------------------
写一个名为permate 的函数.它能输出列表的全部6种排列.
> (permate '( 1 2 3))
( ( 1 2 3 ) ( 1 3 2 ) // 以1 打头
( 2 1 3 ) ( 2 3 1) // 以2打头
( 3 1 2 ) ( 3 2 1) ) // 以3打头
接收一个长度为n的列表,输出的是一个长度为 n! 的列表.
( define ( permate items)
( if ( null?items ) '(())
( apply append
( map ( lambda (elem)
( map ( lambda ( permatation )
( cons elem permatation ) )
( permatation ( remove items elem ) ) ) )
items ) )
//------------------------------------------------
Scheme程序编写技巧:
其程序可看作由一部分一部分拼起来,在写的过程中,可以不按程序运算的逻辑顺序来写.( 可以先写后面的再写前面的, 先写中间的再写两头的等等. )
Scheme的许多内置函数后面都维护着一个数据结构,因此用户只需要调用内置函数,而无需自已再根据应用来分配、调度和管理内存;无需用户自已来维护相应的数据结构。
Scheme的程序运行的执行过程,可以分为三个步骤:输入-评估-打印.
不论用户输入的数据是什么类型,Scheme都会在后台产生一个链表,接收输入的数据后将其填写到链表相应的结点中,并标明数据类型,最后返回这个链表的首地址,为后面的评估过程作准备。
> '(1 2 3)
( 1 2 3 )
> ( cons 1 ( cons 2 ( cons 3 '() ) ) )
( 1 2 3 )
以上两段代码是等价的。
cons作为Scheme的一个符号是依附在一段代码上的,解释器很熟悉,解释器遇到cons后就知道怎么分配一段内存给相关的数据,分配好后,它还要知道在各已分配内存空间中需要放入什么数据。
以上为Scheme的基本工作原理,可以用C++模拟写一个min版的Scheme解释器。
Lecture23
Scheme的内存分配和管理
> ( define seq '(1 2 3) ) // 此定义将名为seq的变量与结点值域分别为1, 2, 3的链表关联起来. seq 为一个全局变量.
> ( car seq ) // car 评估并获取全局变量seq 所指身的链表的首结点的数据域的值.
> ( cons '(1 2 3) '(4 5 6) )
((1 2 3 ) 4 5 6)
在cons运算之前, 系统会生成两个链表,一个链表的结点分别为: 1 2 3. 另一个链表的结点分别为 : 4 5 6. cons运算后, 会生成一个新结点. 结点的值域保存链表 1 2 3 的首地址, 结点的指针域保存链表 4 5 6 的首地址.
> ( cons '(1 2) '(1 2) ) // 1
( ( lambda (x) ( cons x x ) ) '( 1 2 ) ) // 2
由于打印函数对内存管理视若无睹. 打印函数只简单地要求打印列表的car部分和cdr部分. 所以以上两个函数的打印结果是一样的. 但实际上,两个函数的内存分布是不一样的.第二个函数产生的只有三个结点的链表,第二和第三个结点分别保存值1 2. 而首结点的值域和指针域同时指向第二结点.
//--------------------------------------------------------------
> ( map car '( (1 2) (3 4) (5 6 7) ) ) // map 可带任意个参数
(1 3 5)
> ( map + '(1 2) '(10 20) '(100 400) )
(111 422)
> ( map * '(1) '(2) '(3) '(4) '(5) )
(120)
实现:
( define ( unary-map fn seq ) ) // 比较Lecture21中的my_unary_map
( if ( null ? seq) ()
( cons ( fn (car seq) )
( unary-map fn ( cdr seq ) ) ) ) )
因为:
( define ( bar a b c . d)
( list a b c d ) )
> ( bar 1 2 3 4 5 6 )
( 1 2 3 ( 4 5 6 ))
> ( bar 1 2 3 )
( 1 2 3 ())
也就是说点"." 表示其后无论有多少个参数, 都将被收集起来放到参数列表中.
所以map的定义为:
( define ( map fn first-list other-list)
( if ( null ? first-list) '()
( cons ( apply fn
( cons ( car first-list )
( unary-map car other-list ) ) )
( apply map
( cons fn
( cons ( cdr first-list )
( unary-map cdr other-list ) ) ) ) ) ) )
map 底层是依靠cons来帮助它分配空间的. cons调用时Scheme会自动生成一个cons区域.
当打印输出完成后, 之前cons分配得到的cons内存区域就会被回收.
例子:
>( define x '(1 2 3) ) // 变量x与链表( 1 2 3 )关联起来了.
>( define y (cdr x) )
>( define x '(4 5) ) // 这样一来结点结点 1 就无变量指向了. 某些系统会给每个结点设一个计数器, 当计数器为0时, 由垃圾回收器自动回收.
Scheme的垃圾回收机制:
所有变量(如: x, y)放在Symbal map集中, 系统自动生成的链表结点放在mast cons set集中, 每个单元结点都可以被单独释放. 然后根据define的定义将变量与链表结点关联起来.
系统会根据算法, 在需要的时候对Symbal map中的所有变量作一次搜索. 对mast cons set中与之有关联的结点作上标记. 然后令mast cons set收回其集中所有未做标记的结点.
//-----------------------------------------------------------------
ML 是Scheme的增强版
Harkll
Erlang 优势: 并行处理
C语言是由写unix的人发明的, 目的是使写操作系统更容易.
C++在70年代末~80年代初开始出现.
Python从2000年开始流行.
循环的一轮被称为一次迭代, 所谓用迭代来代替递归, 就是用循环来代替递归. 反过来, 递归也可以用来替换迭代. 如scheme语言所示.
"hello there".statswith("...")
"hello there".capitalize()
"Hello There".istitle()
"[ ]" 表列表且此列表可变.
//--------------------------------
smallnum = [ 1, 2, 3, 4, 5, 6]
len( smallnum )
smallnum[ -2 ]
smallnum[ len( smallnum ) - 1 ]
smallnum[ 1 : 3 ]
smallnum[ 0 : 3 ]
smallnum[ 0 : 0 ]
smallnum[ -2 : ]
smallnum[ 1 : -1 ]
smallnum[ 1 : 0 ]
smallnum[ 1 : ]
"hello there"[ 4 : 7 ]
//--------------------------------
seq = [ 0, 1, 2, 3, 5, 6, 7, 8]
seq[ 4 : 4 ] = [ 4 ]
seq
//--------------------------------
seq = [ 5, 9, 2, 0 ]
seq
seq[ 0 ] = 14
seq
//--------------------------------
seq = ( 1, 2, 3, 4 )
seq[ 1 ] = 14
//--------------------------------
seq = [ "a", "b", "c"]
seq.append( "d" )
seq
//----------------------------------------------
在名为 divisors.py 的文件中作如下定义:
def getherDivisors( number ) :
divisors = []
for div in range( 1, number+1 ) :
if number % div == 0:
divisors.append( div )
return divisors
每一行的缩进靠制表符设定.
三重引号对表注释字符串将出现在多行中.
在其它模块中引用函数getherDivisors的方式有两种.
方法1:
>>> import divisors
>>> divisors.getherDivisors( 24 )
方法2:
>>> from divisors import getherDivisors
>>> getherDivisors( 24 )
//--------------------------------------------------
字典
>>> student = {} // 大括号对描述一个字典常量的所有内容.
>>> student
{}
>>> student["name"] = "Linda"
...
>>> student["gpa"] = 3.56
>>> student
...
>>> student["gpa"]
...
>>> student["gpa"] = 4.5
...
Python中的所有对象( 如: 列表, 字典等 )都是通过引用传递的.
>>> student["friends"] = ["aa", "bb", "cc"]
>>> student
...
>>> student["ideas"] = {}
>>> student
...
>>> playground = { }
>>> playground[ 4 ] = "think"
>>> playground
...
Lecture25
字典是Python的基础
grammar = { '<start>' : [ [ 'This', '<object>', 'is here.' ] ],
'<object>' : [ [ 'computer' ],
[ 'car' ],
[ 'assignment' ] ] };
在Scheme中可以用序列化的对象结构来表达数据.
本例中:
grammar在内存中的字典有两个key分别是 start 和 object. 它们各自分别映射到一个list的列表. start对应的列表长度为1, object对应的列表长度为3.
import sys
from random import choice, seed
def expand( symbol )
if symbol.startswith( '<' ): // '<' 表尖括号
definitions = grammar[ symbol ]
expansion = choice( definitions ) // 实际上以一个整数或一个列表作为参数, choice是内置函数, 用以获得随机数. 若输入n, 则choice返回一个介于0与n之间的随机数 // 若输入一个列表, 则choice等概率地随机选取一个此列表的元素, 并返回此元素.
map( expand, expansion ) // 用递归而非迭代的方法来得到所有值.
else
sys.out.write(symbol)
运行:
>>> seed()
>>> expand( '<start>' )
若将grammar作为参数则上例可改为
def expand( symbol, grammar )
if symbol.startswith( '<' ):
definitions = grammar[ symbol ]
expansion = choice( definitions )
map( expand, expansion ) // map 只能映射带一个参数的一元函数.
// lambdas 实际上在Python中也存在, 此句也可表达为:
map( lambdas, item: expand ( item, grammar ), expansion )
else
sys.out.write(symbol)
运行:
>>> seed()
>>> expand( '<start>', grammar )
//---------------------------------------------------------------Python中的各种对象实际上是通过引用( 别名 )被四处传递的.
>>> x = [1, 2, 3]
>>> x
[1, 2, 3]
>>> y = x
>>> y
[1, 2, 3]
>>> x.append(4)
>>> x
[1, 2, 3, 4]
>>> y
[1, 2, 3, 4]
>>> z = [10, 12, 14]
>>> z
[10, 12, 14]
>>> w = [z, z]
>>> w
[ [10, 12, 14], [10, 12, 14] ]
>>> z .append( 17 )
>>> z
[10, 12, 14, 17]
>>> w
[[10, 12, 14, 17], [[10, 12, 14, 17]]
若不想通过引用, 而对赋值变量分配新的内存空间, 则需要使用浅拷贝或深度copy贝.
from copy import copy, deepcopy
>>> x = [14, 15, 21]
>>> x
[14, 15, 21]
>>> y = x
>>> y
[14, 15, 21]
>>> x is y // is 为内置
True
>>> z = copy( x ) // 只进行浅拷贝( 一层拷贝 )
>>> z
[14, 15, 21]
>>> z is x
False
>>> m = [ 1, 2, 3 ] // 内存中生成三个结点组成的链表, 链表首地址赋给m, 结点的值域中的值分别为1, 2, 3.
>>> n = [m, m] // 内存中生成两个结点组成的链表, 链表首地址赋给n, 两个结点的值域都指向链表m
>>> p = deepcopy( n ) //
//------------------------------------------------------------
Python 中的类和对象.
Python中的类和对象的实现都是以字典为基础的. 从内存的角度看, Python中类与字典的实现机制是一样的. Python, Object-C等语言都是把类( 或结构 )中的field作为字符串储存在字典中的.
lex.py 文件:
class lexicon:
def __init__( self, filename = 'words' ) : // self 是从Object-C中" 借来"的
infile = open( filename, 'r' )
words = infile.readlines()
self.words = [ ] // Python对象的内存最初被初始化为空, 当调用此self.words = ... 时就会为其中添加一个名为words的成员. 这一点与C/C++等在语言是不 同的后者在为对象分配完内存空间后, 此空间包含哪些成员变量, 它们又是如何分布的就已经确定了.
for word in words :
length = len( word ) - 1
self.words.append( word[ : length ] )
def ContainsWord( self, word ) :
return self.words[ bisect( self.words, word ) - 1 ] == word
Python实际类对象通过一个字典来建模, C/C++的所有事情都是在编译时完成的.
使用:
>>> from lex import lexicon
>>> el = lexicon()
>>> lexicon.__dict__ // 此句可以得到与lexicon相对应的字典内存分布说明. 其含有所有嵌入在内部的符号的列表.
>>> el.words = [ ] // Python 无"私有成员变量" 或 "私有成员函数" 的概念. 此句清空了lexicon类对象el的self.world成员变量, 此成员变量在__init__函数中曾被初始化.
>>> el.__dict__
[ 'words' : [ ] ]
//-------------------------------------------------------------
>>> o = object() // Python将自动产生一个Object对象
>>> o
<...>
>>> o.__dict__
{ }
>>> o.a = 17 // o.__dict__[ 'a' ] = 17
>>> o.b = "hello" // o.__dict__[ 'b' ] = "hello"
>>> o.c = False // o.__dict__[ 'c' ] = False
>>> o.__dict__
{ 'a':17, 'b':"hello", 'c':False }
Python所使用的对象是动态可扩展的, C/C++ , Java的所有任何代码其对象大小在对象一产生后就已经确定了.
Python是一种动态执行可扩展语言, 在C/C++中函数调用要借助Activation Record, 但Python则无此机制.
Lecture26
Python的XML解析与Internet编程.
Python是较"年轻"的语言, 所以它内置了一些Internet编程所需的函数.
对XML的解析有两种方式:
1. 基于流的方式: 不将全部信息完整地存储到内存中,在任何时候只存储整个文本流的一个子集.
课程中自定义XML文件结构:
<title> ... </title>
<channel>
<item>
<title> Article Title </title>
...
</item>
<item>
...
</item>
</channel>
from urllib2 import urlopen
from xml.sax import make.parser
import sys
def ListFeedTitles( url ) :
infile = urlopen( url ) :
parser = make.parser()
parset.setContentHandler( RSSHandler() )
parser.parse( infile )
class ContentHandler:
def __init__(self) :
ContentHandler.__init__(self)
self.__inItem = False
self.__inTitle = False // 变量前加双下划线,表此变量为类成员变量.Python中类的私有变量前必须强制加双下划线,可以在类内部直接引用但在类外部引用会 // 出错.
def Characters(sellf, data) :
if self.__inTitle:
sys.stdout.write( data )
def startElement( self, tag, attrs ) :
if tag == "item" : self.__inItem = True
if tag == "title" and self.__inItem :
self.__inTitle = True
def endElement( self, tag )
if tag == "title" and self.__inTitle :
sys.stdout.write("\n")
self.__inTitle = False
if tag == "item"
self.__inItem = False
// 上例中的startElement与endElement主要作用是维护__inTitle和__inItem两个成员变量.
2. 称为SAX方法
将XML的文档数据整个完整地存储到内存中.( XML文档整个是一个树型结构 )
因此,可以在内存中对XML的相关数据进行修改、增删。
这样一来,可以实现网页浏览时的交互操作:比如,购物网站上用户点击按钮将物品装入购物篮中等。
Lecture27
Haskell是与Scheme类似的函数式编程语言. 官网:http://www.haskell.org/haskellwiki/Haskell
从2003年出现开始, 逐渐由一种研究型语言走向实际开发应用.
与Scheme一样, 函数式编程不考虑机器如何来执行, 执行顺序如何, 而给出了问题求解的数学公式就可以直接由此得到全部的程序.
与Scheme一样, 函数可以作为一个类型在程序中传递. ( 编程原本中将函数作为类型来考虑, 它们之间是否有共通的理论基础? )
与Scheme不一样, Haskell有类型检测.
与Python不一样, Haskell不是面向对象的.
Haskell在定义用户自已的数据类型时, 用的形式有点象南大出版的《编译原理》(张幸儿)中 介绍的语义表达方法。