11.4.1 無窮列表
這一節的標題可能聽起來有點奇怪(或瘋了),所以,我們會提供乙個字面解釋。我們用得很多的資料結構之一是函式式列表。我們也可能想要表示邏輯上無窮列表,例如,所有質數的列表。事實上,我們不會使用所有的數字,但可能會處理這樣的資料結構,而不考慮長度。如果列表是無窮的,我們就能夠訪問我們需要盡可能多的數字。
除了數學上的挑戰,同樣的概念在許多主流程式設計中也是有用的。當我們在第 4 章畫餅圖時,用的是隨機顏色,但是,如果使用以無限列表方式生成顏色,使得圖表外觀清晰。在下一章中,我們將會看到所有這些例子,但現在,我們來向你展示如何將這種想法表示成使用延遲值。
在記憶體中儲存無窮數字列表,看起來是乙個棘手的問題。很明顯,我們不能完整地儲存這種資料結構,所以,我們需要儲存它的一部分,其餘部分表示為乙個延遲計算。如我們已經看到的,延遲值是乙個好方法,表示延遲計算的部分。
我們可以表示簡單的無窮列表,其方式類似於普通列表。它是乙個單元格,它包含乙個值和列表中的其餘部分。唯一不同的是,列表的其餘部分將延遲計算,當我們執行它的時候,它給出另乙個單元格。在 f# 中,我們可以使用差別聯合來表示這種列表,如清單 11.19 所示。
listing 11.19 infinite list of integers (f#)
type infiniteints =
| lazycell of int *
lazy
這個差別聯合只有乙個識別器,這意味著,它是類似於記錄。我們可以用記錄來寫這段**,但對於這個示例,用差別聯合更方便,因為,我們可以用優美的模式匹配的語法,來提取其攜帶的值。這個唯一的識別器被稱為延遲單元(lazycell),它在單元格儲存當前值,以及對"尾"的引用。尾是乙個延遲值,所以,它將在需求時進行計算。用這種方式,當我們計算單元格時,可以逐個列表單元格進行,結果會被快取。
f# 和 haskell 中的延遲列表
如前所述,haskell 到處使用延遲計算。這意味著,在 haskell 中,標準的列表型別自動就是延遲的。尾直到這個值在**中訪問時才計算。
在 f# 中,延遲列表的使用並不非常頻繁。在下一章,我們將會看到一種更優雅的寫無窮集合,用 f#,也用 c# 2.0。f# 提供了延遲列表的一種實現,相似於我們在這一節實現的。你可以在 lazylist fsharp.powerpack.dll 庫中找到 lazylist<'a>。
現在,我們已經有了自己的型別,讓我們用它來建立乙個簡單的無窮列表,儲存整數0、 1、 2、 3 … …。清單 11.20 也演示如何從這個列表中訪問值。
listing 11.20 creating a list containing 0, 1, 2, 3, 4, … (f# interactive)
> let rec numbers(num) =
lazycell(num, lazy numbers(num + 1));;
val numbers : int –> infiniteints
> numbers(0);;
val nums : infiniteints = lazycell(0, value is not created.)
> let next(lazycell(hd, tl)) =
tl.value;;
val next : infiniteints –> infiniteints
> numbers(0) |> next |> next |> next |> next |> next;;
val nums : infiniteints = lazycell(5, value is not created.)
我們首先寫乙個遞迴函式 numbers,它返回整數的無窮列表,從作為引數值給定的數字開始,直到無窮。返回的單元格包含第乙個值和乙個尾。這個尾是延遲值,在以遞迴方式呼叫 numbers 時,得到下乙個單元格。
如果我們以 0 作為引數值,呼叫這個函式,得到從 0 開始的無窮列表,f# interactive 的輸出並不具特別可讀性,但你可以發現,第乙個值是 0,尾是 型別的延遲值。隨後的命令宣告函式 next,給出列表中下乙個單元格。我們用宣告中的模式匹配來分解唯一的引數值。這看起來有點怪,因為你通常不使用只有乙個識別器的差別聯合,但其原理與分解元組的元件是相同。在函式體中,我們讀取 value 屬性,計算下乙個單元格。最後一行使用 next 函式幾次,從列表中讀第六次值。
我們可以用延遲列表做更多的事情,但是,在這裡我們不會更深入進去,在下一章,我們會看到更多 f# 常用的技術。有些情況下,lazylist<'t> 型別是很有用的。雖然我們沒有直接使用 f# 型別庫,使用它也不會有問題,現在你理解了原理。
在這一節介紹無窮資料結構中,我們一直專注於更多的函式風格,而沒有展示 c# 示例。現在,它有可能在 c# 中寫相同的型別,我們知道如何在 c# 中寫延遲值,但在下一章中,我們將會看到更自然的方式在 c# 中表示無窮結構,或者流的值。
在此示例中,延遲列表有乙個非常有趣的地方。一旦我們計算列表到一些點,計算的值在記憶體中仍可用,我們不必要每次重新計算。正如我們在下一節中將要看到,延遲值的這方面可以用於很簡單而優雅的快取機制。
寫處理無窮列表的函式
當使用標準的列表型別時,我們可以使用的函式,如 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...