11 4 1 無窮列表

2021-06-28 05:55:58 字數 2674 閱讀 4605

11.4.1 無窮列表

這一節的標題聽起來可能有點奇怪(或瘋狂),所以,需要解釋一下。函式式列表是我們經常使用的一種資料結構。如果我們想要表示邏輯上無窮的列表,例如,所有質數的列表。當然,我們不可能用到所有的數字,只是像這樣使用資料結構,而不必考慮長度。如果列表是無窮的,我們就能夠訪問盡可能多的數字,只要我們需要。

除了數學上的挑戰以外,同樣的概念在許多主流程式設計中也是有用的。當我們在第四章繪製餅圖時,用的是隨機顏色,而如果使用以無窮列表方式生成顏色,會使得圖表外觀清晰。在下一章,我們將會看到所有這些例子,但現在,我們要把這種用延遲值表示出來。

在記憶體中儲存無窮數字的列表,就是非常棘手的問題。很明顯,我們不可能完整地儲存完整的資料結構,因此,需要儲存一部分,而其餘部分用延遲計算表示。我們已經知道,用延遲值表示延遲計算的部分是乙個好方法。

表示簡單的無窮列表的方法,與普通列表類似,單元格包含乙個值和對列表其餘部分[的引用];唯一不同的是,列表的其餘部分需要延遲計算,當執行的時候,會給出另乙個單元格。在 f# 中,我們使用差別聯合來表示這種列表,如清單 11.19 所示。

清單 11.19 整數的無窮列表 (f#)

type infiniteints =

|lazycell of int *     [1]

lazy[2]

這個差別聯合只有乙個識別器,因此,與記錄類似。當然,我們可以用記錄來寫**,但對於這個示例,用差別聯合更方便,因為我們可以用優美的模式匹配語法,來提取包含的值。唯一的識別器稱lazycell(延遲單元格),在單元格儲存了當前值[1],以及對「尾」的引用[2]。尾是延遲值,因此,將在需要時進行計算。用這種方式,當我們計算單元格時,通過逐個單元格就能計算出列表,結果會被快取起來。

f# 和 haskell 中的延遲列表

如前所述,haskell 中各處都使用延遲計算,因此,haskell 中標準的列表型別,自動就是延遲的,尾直到值在**中被訪問時才計算。

而在 f# 中,延遲列表的使用並不非常頻繁。在下一章,我們將會看到用 f# 以及 c# 2.0,以更優雅方法寫無窮集合。f# 對延遲列表的實現方法,與我們在這一節實現的型別相似,在 lazylist fsharp.powerpack.dll 庫中可以找到lazylist<'a>。

已經有了我們自己的型別以後,就可以用它來建立簡單的無窮列表,儲存整數0、1、2、3…了。清單 11.20 演示了如何訪問列表中的值。

清單 11.20 建立包含 0, 1, 2, 3, 4, … 的列表(f# interactive)

> let rec numbers(num) =   [1]

lazycell(num, lazy numbers(num + 1));;  <-- 建立下乙個單元格為延遲計算

val numbers : int -> infiniteints

> numbers(0);;   [2]

val nums : infiniteints = lazycell(0, valueis not created.)

> let next(lazycell(hd, tl)) =   [3]

tl.value;;   <-- 計算表示下乙個單元格的延遲值

val next : infiniteints -> infiniteints

> numbers(0) |> next |> next |>next |> next |> next;;   <-- 訪問列表中的六個值

val nums : infiniteints = lazycell(5, valueis not created.)

我們首先寫乙個遞迴函式 numbers [1],它返回整數的無窮列表,從作為引數值給定的數字開始,直到無窮。返回的單元格包含第乙個值和尾,尾是延遲值,(在計算時)遞迴方式呼叫 numbers,得到下乙個單元格。

如果我們以 0 作為引數值,呼叫函式,得到從 0 開始的無窮列表[2]。f# interactive 的輸出可讀性不強,但可以發現,第乙個值是 0,尾是 型別的值。隨後的命令宣告函式 next,得到列表中下乙個單元格[3]。我們使用宣告中的模式匹配,來分解唯一的引數值。這看起來有點怪,因為我們通常不使用只有乙個識別器的差別聯合,但與把元組分解元件的原理是相同的。在函式體中,我們讀取 value 屬性,計算下乙個單元格。最後一行多次使用 next 函式,從列表中讀第六次值。

我們可以用延遲列表做更多的事情,但是,在這裡我們不會太深入,在下一章,我們會看到f# 更多的習慣用法。有些情況下,lazylist<'t> 型別是很有用的。雖然我們沒有直接使用 f#庫中的型別,只要懂得了原理,使用起來也不會有問題。

在此示例中,延遲列表有乙個很重要方面。只要我們在某一點計算過列表,計算的值在記憶體中一直可用,因此,不必要每次重新計算。正如我們在下一節將要看到,延遲值的這個特徵可以用作簡單而優雅的快取機制。

寫處理無窮列表的函式

處理標準的列表型別時,我們可以用函式,如 list.map 和 list.filter;對於無窮列表,我們也可以實現同樣的函式,但是,當然不是所有的都可以。例如,list.fold 和 list.sort 需要讀所有元素,而對於延遲列表滅族,這是不可能的。作為乙個可能的例子,這裡實現了 map 函式:

let rec map f (lazycell(hd, tl)) =

lazycell(f(hd), lazy map f tl.value)

結構類似於正常的 map 函式,把給定的函式應用到單元格中的第乙個值,然後,遞迴處理列表中的其餘部分。對尾的處理,由於使用 lazy 關鍵字而延遲。其他常見的列表處理函式也類似。

11 4 1 無窮列表

11.4.1 無窮列表 這一節的標題可能聽起來有點奇怪 或瘋了 所以,我們會提供乙個字面解釋。我們用得很多的資料結構之一是函式式列表。我們也可能想要表示邏輯上無窮列表,例如,所有質數的列表。事實上,我們不會使用所有的數字,但可能會處理這樣的資料結構,而不考慮長度。如果列表是無窮的,我們就能夠訪問我們...

11 4 1 無窮列表

11.4.1 無窮列表 這一節的標題可能聽起來有點奇怪 或瘋了 所以,我們會提供乙個字面解釋。我們用得很多的資料結構之一是函式式列表。我們也可能想要表示邏輯上無窮列表,例如,所有質數的列表。事實上,我們不會使用所有的數字,但可能會處理這樣的資料結構,而不考慮長度。如果列表是無窮的,我們就能夠訪問我們...

無窮大無窮小

如果問題中各資料的範圍明確,那麼無窮大的設定不是問題,在不明確的情況下,很多程式 員都取0x7fffffff作為無窮大,因為這是32 bit int的最大值。如果這個無窮大只用於一般的比較 比如求最小值時min變數的初值 那麼0x7fffffff確實是乙個完美的選擇,但是在更多的情況下,0x7fff...