摘要:示例有理數的算術有理數可表示為整數的比值,并且它組成了實數的一個重要子類。有理數的值需要兩部分來描述。這里的重要概念是,通過將有理數表示為整數的比值,我們能夠完全避免近似問題。返回有理數的分子。
2.2 數據抽象
來源:2.2 Data Abstraction
譯者:飛龍
協議:CC BY-NC-SA 4.0
由于我們希望在程序中表達世界中的大量事物,我們發現它們的大多數都具有復合結構。日期是年月日,地理位置是精度和緯度。為了表示位置,我們希望程序語言具有將精度和緯度“粘合”為一對數據的能力 -- 也就是一個復合數據結構 -- 使我們的程序能夠以一種方式操作數據,將位置看做單個概念單元,它擁有兩個部分。
復合數據的使用也讓我們增加程序的模塊性。如果我們可以直接將地理位置看做對象來操作,我們就可以將程序的各個部分分離,它們根據這些值如何表示來從本質上處理這些值。將某個部分從程序中分離的一般技巧是一種叫做數據抽象的強大的設計方法論。這個部分用于處理數據表示,而程序用于操作數據。數據抽象使程序更易于設計、維護和修改。
數據抽象的特征類似于函數抽象。當我們創建函數抽象時,函數如何實現的細節被隱藏了,而且特定的函數本身可以被任何具有相同行為的函數替換。換句話說,我們可以構造抽象來使函數的使用方式和函數的實現細節分離。與之相似,數據抽象是一種方法論,使我們將復合數據對象的使用細節與它的構造方式隔離。
數據抽象的基本概念是構造操作抽象數據的程序。也就是說,我們的程序應該以一種方式來使用數據,對數據做出盡可能少的假設。同時,需要定義具體的數據表示,獨立于使用數據的程序。我們系統中這兩部分的接口是一系列函數,叫做選擇器和構造器,它們基于具體表示實現了抽象數據。為了演示這個技巧,我們需要考慮如何設計一系列函數來操作有理數。
當你閱讀下一節時,要記住當今編寫的多數 Python 代碼使用了非常高級的抽象數據類型,它們內建于語言中,比如類、字典和列表。由于我們正在了解這些抽象的工作原理,我們自己不能使用它們。所以,我們會編寫一些不那么 Python 化的代碼 -- 它并不是在語言中實現我們的概念的通常方式。但是,我們所編寫的代碼出于教育目的,它展示了這些抽象如何構建。要記住計算機科學并不只是學習如何使用編程語言,也學習它們的工作原理。
2.2.1 示例:有理數的算術有理數可表示為整數的比值,并且它組成了實數的一個重要子類。類似于1/3或者17/29的有理數通常可編寫為:
/
其中,
有理數在計算機科學中很重要,因為它們就像整數那樣,可以準確表示。無理數(比如pi 或者 e 或者 sqrt(2))會使用有限的二元展開代替為近似值。所以在原則上,有理數的處理應該讓我們避免算術中的近似誤差。
但是,一旦我們真正將分子與分母相除,我們就會只剩下截斷的小數近似值:
>>> 1/3 0.3333333333333333
當我們開始執行測試時,這個近似值的問題就會出現:
>>> 1/3 == 0.333333333333333300000 # Beware of approximations True
計算機如何將實數近似為定長的小數擴展,是另一門課的話題。這里的重要概念是,通過將有理數表示為整數的比值,我們能夠完全避免近似問題。所以出于精確,我們希望將分子和分母分離,但是將它們看做一個單元。
我們從函數抽象中了解到,我們可以在了解某些部分的實現之前開始編出東西來。讓我們一開始假設我們已經擁有一種從分子和分母中構造有理數的方式。我們也假設,給定一個有理數,我們都有辦法來提取(或選中)它的分子和分母。讓我們進一步假設,構造器和選擇器以下面三個函數來提供:
make_rat(n, d)返回分子為n和分母為d的有理數。
numer(x)返回有理數x的分子。
denom(x)返回有理數x的分母。
我們在這里正在使用一個強大的合成策略:心想事成。我們并沒有說有理數如何表示,或者numer、denom和make_rat如何實現。即使這樣,如果我們擁有了這三個函數,我們就可以執行加法、乘法,以及測試有理數的相等性,通過調用它們:
>>> def add_rat(x, y): nx, dx = numer(x), denom(x) ny, dy = numer(y), denom(y) return make_rat(nx * dy + ny * dx, dx * dy) >>> def mul_rat(x, y): return make_rat(numer(x) * numer(y), denom(x) * denom(y)) >>> def eq_rat(x, y): return numer(x) * denom(y) == numer(y) * denom(x)
現在我們擁有了由選擇器函數numer和denom,以及構造器函數make_rat定義的有理數操作。但是我們還沒有定義這些函數。我們需要以某種方式來將分子和分母粘合為一個單元。
2.2.2 元組為了實現我們的數據抽象的具體層面,Python 提供了一種復合數據結構叫做tuple,它可以由逗號分隔的值來構造。雖然并不是嚴格要求,圓括號通常在元組周圍。
>>> (1, 2) (1, 2)
元組的元素可以由兩種方式解構。第一種是我們熟悉的多重賦值:
>>> pair = (1, 2) >>> pair (1, 2) >>> x, y = pair >>> x 1 >>> y 2
實際上,多重賦值的本質是創建和解構元組。
訪問元組元素的第二種方式是通過下標運算符,寫作方括號:
>>> pair[0] 1 >>> pair[1] 2
Python 中的元組(以及多數其它編程語言中的序列)下標都以 0 開始,也就是說,下標 0 表示第一個元素,下標 1 表示第二個元素,以此類推。我們對這個下標慣例的直覺是,下標表示一個元素距離元組開頭有多遠。
與元素選擇操作等價的函數叫做getitem,它也使用下標以 0 開始的位置來在元組中選擇元素。
元素是原始類型,也就是說 Python 的內建運算符可以操作它們。我們不久之后再來看元素的完整特性。現在,我們只對元組如何作為膠水來實現抽象數據類型感興趣。
表示有理數。元素提供了一個自然的方式來將有理數實現為一對整數:分子和分母。我們可以通過操作二元組來實現我們的有理數構造器和選擇器函數。
>>> def make_rat(n, d): return (n, d) >>> def numer(x): return getitem(x, 0) >>> def denom(x): return getitem(x, 1)
用于打印有理數的函數完成了我們對抽象數據結構的實現。
>>> def str_rat(x): """Return a string "n/d" for numerator n and denominator d.""" return "{0}/{1}".format(numer(x), denom(x))
將它與我們之前定義的算術運算放在一起,我們可以使用我們定義的函數來操作有理數了。
>>> half = make_rat(1, 2) >>> str_rat(half) "1/2" >>> third = make_rat(1, 3) >>> str_rat(mul_rat(half, third)) "1/6" >>> str_rat(add_rat(third, third)) "6/9"
就像最后的例子所展示的那樣,我們的有理數實現并沒有將有理數化為最簡。我們可以通過修改make_rat來補救。如果我們擁有用于計算兩個整數的最大公約數的函數,我們可以在構造一對整數之前將分子和分母化為最簡。這可以使用許多實用工具,例如 Python 庫中的現存函數。
>>> from fractions import gcd >>> def make_rat(n, d): g = gcd(n, d) return (n//g, d//g)
雙斜杠運算符//表示整數除法,它會向下取整除法結果的小數部分。由于我們知道g能整除n和d,整數除法正好適用于這里。現在我們的
>>> str_rat(add_rat(third, third)) "2/3"
符合要求。這個修改只通過修改構造器來完成,并沒有修改任何實現實際算術運算的函數。
擴展閱讀。上面的str_rat實現使用了格式化字符串,它包含了值的占位符。如何使用格式化字符串和format方法的細節請見 Dive Into Python 3 的格式化字符串一節。
2.2.3 抽象界限在以更多復合數據和數據抽象的例子繼續之前,讓我們思考一些由有理數示例產生的問題。我們使用構造器make_rat和選擇器numer和denom定義了操作。通常,數據抽象的底層概念是,基于某個值的類型的操作如何表達,為這個值的類型確定一組基本的操作。之后使用這些操作來操作數據。
我們可以將有理數系統想象為一系列層級。
平行線表示隔離系統不同層級的界限。每一層上,界限分離了使用數據抽象的函數(上面)和實現數據抽象的函數(下面)。使用有理數的程序僅僅通過算術函數來操作它們:add_rat、mul_rat和eq_rat。相應地,這些函數僅僅由構造器和選擇器make_rat、numer和and denom來實現,它們本身由元組實現。元組如何實現的字節和其它層級沒有關系,只要元組支持選擇器和構造器的實現。
每一層上,盒子中的函數強制劃分了抽象的邊界,因為它們僅僅依賴于上層的表現(通過使用)和底層的實現(通過定義)。這樣,抽象界限可以表現為一系列函數。
抽象界限具有許多好處。一個好處就是,它們使程序更易于維護和修改。很少的函數依賴于特定的表現,當一個人希望修改表現時,不需要做很多修改。
2.2.4 數據屬性我們通過實現算術運算來開始實現有理數,實現為這三個非特定函數:make_rat、numer和denom。這里,我們可以認為已經定義了數據對象 -- 分子、分母和有理數 -- 上的運算,它們的行為由這三個函數規定。
但是數據意味著什么?我們還不能說“提供的選擇器和構造器實現了任何東西”。我們需要保證這些函數一起規定了正確的行為。也就是說,如果我們從整數n和d中構造了有理數x,那么numer(x)/denom(x)應該等于n/d。
通常,我們可以將抽象數據類型當做一些選擇器和構造器的集合,并帶有一些行為條件。只要滿足了行為條件(比如上面的除法特性),這些函數就組成了數據類型的有效表示。
這個觀點可以用在其他數據類型上,例如我們為實現有理數而使用的二元組。我們實際上不會談論元組是什么,而是談論由語言提供的,用于操作和創建元組的運算符。我們現在可以描述二元組的行為條件,二元組通常叫做偶對,在表示有理數的問題中有所涉及。
為了實現有理數,我們需要一種兩個整數的粘合形式,它具有下列行為:
如果一個偶對p由x和y構造,那么getitem_pair(p, 0)返回x,getitem_pair(p, 1)返回y。
我們可以實現make_pair和getitem_pair,它們和元組一樣滿足這個描述:
>>> def make_pair(x, y): """Return a function that behaves like a pair.""" def dispatch(m): if m == 0: return x elif m == 1: return y return dispatch >>> def getitem_pair(p, i): """Return the element at index i of pair p.""" return p(i)
使用這個實現,我們可以創建和操作偶對:
>>> p = make_pair(1, 2) >>> getitem_pair(p, 0) 1 >>> getitem_pair(p, 1) 2
這個函數的用法不同于任何直觀上的,數據應該是什么的概念。而且,這些函數滿足于在我們的程序中表示復合數據。
需要注意的微妙的一點是,由make_pair返回的值是叫做dispatch的函數,它接受參數m并返回x或y。之后,getitem_pair調用了這個函數來獲取合適的值。我們在這一章中會多次返回拆解這個函數的話題。
這個偶對的函數表示并不是 Python 實際的工作機制(元組實現得更直接,出于性能因素),但是它可以以這種方式工作。這個函數表示雖然不是很明顯,但是是一種足夠完美來表示偶對的方式,因為它滿足了偶對唯一需要滿足的條件。這個例子也表明,將函數當做值來操作的能力,提供給我們表示復合數據的能力。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/45487.html
摘要:對象表示信息,但是同時和它們所表示的抽象概念行為一致。通過綁定行為和信息,對象提供了可靠獨立的日期抽象。名稱來源于實數在中表示的方式浮點表示。另一方面,對象可以表示很大范圍內的分數,但是不能表示所有有理數。 2.1 引言 來源:2.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 在第一章中,我們專注于計算過程,以及程序設計中函數的作用。我們看到了...
摘要:為通用語言設計解釋器的想法可能令人畏懼。但是,典型的解釋器擁有簡潔的通用結構兩個可變的遞歸函數,第一個求解環境中的表達式,第二個在參數上調用函數。這一章接下來的兩節專注于遞歸函數和數據結構,它們是理解解釋器設計的基礎。 3.1 引言 來源:3.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 第一章和第二章描述了編程的兩個基本元素:數據和函數之間的...
摘要:另一個賦值語句將名稱關聯到出現在莎士比亞劇本中的所有去重詞匯的集合,總計個。表達式是一個復合表達式,計算出正序或倒序出現的莎士比亞詞匯集合。在意圖上并沒有按照莎士比亞或者回文來設計,但是它極大的靈活性讓我們用極少的代碼處理大量文本。 1.1 引言 來源:1.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 計算機科學是一個極其寬泛的學科。全球的分布...
摘要:實踐指南函數的藝術來源譯者飛龍協議函數是所有程序的要素,無論規模大小,并且在編程語言中作為我們表達計算過程的主要媒介。目前為止,我們討論了函數的形式特性,以及它們如何使用。第一行描述函數的任務。 1.4 實踐指南:函數的藝術 來源:1.4 Practical Guidance: The Art of the Function 譯者:飛龍 協議:CC BY-NC-SA 4.0 函...
摘要:程序用于在編程社群的成員之間交流這些想法。在編程中,我們處理兩種元素函數和數據。在中,我們可以使用賦值語句來建立新的綁定,它包含左邊的名稱和右邊的值。例如,它并不能處理賦值語句。這些圖解的必要部分是函數的表示。 1.2 編程元素 來源:1.2 The Elements of Programming 譯者:飛龍 協議:CC BY-NC-SA 4.0 編程語言是操作計算機來執行任務...
閱讀 2526·2021-10-11 10:59
閱讀 2707·2021-09-22 15:49
閱讀 2645·2021-08-13 13:25
閱讀 1287·2019-08-30 13:14
閱讀 2391·2019-08-29 18:45
閱讀 2997·2019-08-29 18:36
閱讀 1488·2019-08-29 13:21
閱讀 1162·2019-08-26 11:44