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 優勢: 並行處理
Lecture25
字典是Python的基礎
grammar = { '
'
[ '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( '
若將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( '
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文件結構:
...
...
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在定義用戶自已的數據類型時, 用的形式有點象南大出版的《編譯原理》(張幸兒)中 介紹的語義表達方法。