序列
序列是一連串的邏輯元素,而所有元素的型別都相同。 當您有大型、已排序的資料集合,但不一定預期使用所有元素時,序列特別有用。 個別序列元素只會視需要計算,因此序列在未使用所有元素的情況下,可提供比清單更好的效能。 序列是以 seq<'T>
型別表示,這是 IEnumerable<T> 的別名。 因此,實作 IEnumerable<T> 介面的任何 .NET 型別都可以當做序列使用。 Seq 模組支援涉及序列的操作。
序列運算式
序列運算式是評估為序列的運算式。 序列運算式可以採用多種形式。 最簡單的形式會指定範圍。 例如,seq { 1 .. 5 }
會建立包含五個元素的序列,包括端點 1 和 5。 您也可以指定兩個雙句點之間的遞增 (或遞減)。 例如,下列程式碼會建立 10 倍數的序列。
// Sequence that has an increment.
seq { 0..10..100 }
序列運算式是由產生序列值的 F# 運算式所組成。 您也可以使用程式設計方式產生值:
seq { for i in 1..10 -> i * i }
上述範例會使用 ->
運算子,可讓您指定運算式,其值將會成為序列的一部分。 只有在後續程式碼的每個部分都傳回值時,才能使用 ->
。
或者,您可以指定 do
關鍵字,後面接續選擇性 yield
:
seq {
for i in 1..10 do
yield i * i
}
// The 'yield' is implicit and doesn't need to be specified in most cases.
seq {
for i in 1..10 do
i * i
}
下列程式碼會產生座標組清單,以及對代表方格之陣列的索引。 請注意,第一個 for
運算式需要指定 do
。
let (height, width) = (10, 10)
seq {
for row in 0 .. width - 1 do
for col in 0 .. height - 1 -> (row, col, row * width + col)
}
序列中使用的 if
運算式是篩選條件。 例如,若只要產生質數序列,假設您具有 int -> bool
型別的 isprime
函式,請依照下列方式建構序列。
seq {
for n in 1..100 do
if isprime n then
n
}
如先前所述,這裡需要 do
,因為沒有與 if
一起的 else
分支。 如果您嘗試使用 ->
,您會收到錯誤,指出並非所有分支都會傳回值。
yield!
關鍵字
有時候,您可能想要將元素序列包含在另一個序列中。 若要將序列包含在另一個序列中,您必須使用 yield!
關鍵字:
// Repeats '1 2 3 4 5' ten times
seq {
for _ in 1..10 do
yield! seq { 1; 2; 3; 4; 5}
}
yield!
的另一種思考方式是,其會壓平合併內部序列,然後在內含序列中包含該序列。
在運算式中使用 yield!
時,所有其他單一值都必須使用 yield
關鍵字:
// Combine repeated values with their values
seq {
for x in 1..10 do
yield x
yield! seq { for i in 1..x -> i}
}
上一個範例除了針對每個 x
產生從 1
到 x
的所有值之外,還會產生 x
的值。
範例
第一個範例使用包含反覆項目、篩選條件和產生陣列的序列運算式。 此程式碼會將介於 1 到 100 之間的質數序列列印到主控台。
// Recursive isprime function.
let isprime n =
let rec check i =
i > n / 2 || (n % i <> 0 && check (i + 1))
check 2
let aSequence =
seq {
for n in 1..100 do
if isprime n then
n
}
for x in aSequence do
printfn "%d" x
下列範例會建立乘法資料表,其中包含三個元素的元組,各包含兩個因素和乘積:
let multiplicationTable =
seq {
for i in 1..9 do
for j in 1..9 -> (i, j, i * j)
}
下列範例示範如何使用 yield!
將個別序列合併成單一最終序列。 在此情況下,二進位樹狀目錄中每個樹狀子目錄的序列會串連在遞迴函式中,以產生最終序列。
// Yield the values of a binary tree in a sequence.
type Tree<'a> =
| Tree of 'a * Tree<'a> * Tree<'a>
| Leaf of 'a
// inorder : Tree<'a> -> seq<'a>
let rec inorder tree =
seq {
match tree with
| Tree(x, left, right) ->
yield! inorder left
yield x
yield! inorder right
| Leaf x -> yield x
}
let mytree = Tree(6, Tree(2, Leaf(1), Leaf(3)), Leaf(9))
let seq1 = inorder mytree
printfn "%A" seq1
使用順序
序列支援與清單相同的許多函式。 序列也支援使用索引鍵產生函式進行分組和計數等作業。 序列也支援更多樣化的函式來擷取子序列。
許多資料型別,例如清單、陣列、集合和對應都是隱含序列,因為其為可列舉的集合。 除了實作 System.Collections.Generic.IEnumerable<'T>
的任何 .NET 資料型別之外,採用序列作為引數的函式也適用於任何通用 F# 資料型別。 將這個項目與採用清單作為引數的函式對比,其只能採用清單。 seq<'T>
型別是 IEnumerable<'T>
的型別縮寫。 這表示任何實作泛型 System.Collections.Generic.IEnumerable<'T>
的型別,包括 F# 中的陣列、清單、集合和對應,以及大部分 .NET 集合型別都與 seq
型別相容,而且可以在預期序列的任何位置使用。
模組函式
FSharp.Collections 命名空間中的 Seq 模組包含適用於序列的函式。 這些函式也適用於清單、陣列、對應和集合,因為這些型別都是可列舉的,因此可以視為序列。
建立序列
您可以如先前所述使用序列運算式來建立序列,或使用特定函式。
您可以使用 Seq.empty 建立空序列,或使用 Seq.singleton 建立僅有一個指定元素的序列。
let seqEmpty = Seq.empty
let seqOne = Seq.singleton 10
您可以使用 Seq.init 來建立序列,其元素是使用您提供的函式來建立。 您也會提供序列的大小。 此函式就像 List.init 一樣,不同之處是在您逐一查看序列之前,不會建立元素。 下列程式碼說明使用 Seq.init
。
let seqFirst5MultiplesOf10 = Seq.init 5 (fun n -> n * 10)
Seq.iter (fun elem -> printf "%d " elem) seqFirst5MultiplesOf10
輸出為
0 10 20 30 40
藉由使用 Seq.ofArray 和 Seq.ofList<'T> 函式,您可以從陣列和清單建立序列。 不過,您也可以使用轉換運算子,將陣列和清單轉換成序列。 以下程式碼顯示這兩種技巧。
// Convert an array to a sequence by using a cast.
let seqFromArray1 = [| 1 .. 10 |] :> seq<int>
// Convert an array to a sequence by using Seq.ofArray.
let seqFromArray2 = [| 1 .. 10 |] |> Seq.ofArray
藉由使用 Seq.cast,您可以從弱型別集合建立序列,例如 System.Collections
中定義的序列。 這類弱型別集合具有元素型別 System.Object
,並且使用非泛型 System.Collections.Generic.IEnumerable`1
型別來列舉。 下列程式碼說明如何使用 Seq.cast
將 System.Collections.ArrayList
轉換成序列。
open System
let arr = ResizeArray<int>(10)
for i in 1 .. 10 do
arr.Add(10)
let seqCast = Seq.cast arr
您可以使用 Seq.initInfinite 函式來定義無限序列。 針對這類序列,您會提供函式,以從元素的索引產生每個元素。 因為延遲評估,所以可能會有無限序列;呼叫您指定的函式,即可視需要建立元素。 下列程式碼範例會產生無限的浮點數序列,在此案例中,連續整數平方的倒數交替數列。
let seqInfinite =
Seq.initInfinite (fun index ->
let n = float (index + 1)
1.0 / (n * n * (if ((index + 1) % 2 = 0) then 1.0 else -1.0)))
printfn "%A" seqInfinite
Seq.unfold 會從計算函式產生序列,該函式會採用狀態並加以轉換,以在序列中產生每個後續元素。 狀態只是用來計算每個元素的值,而且可以在計算每個元素時變更。 Seq.unfold
的第二個引數是用來啟動序列的初始值。 Seq.unfold
會使用狀態的選項型別,可讓您傳回 None
值來終止序列。 下列程式碼顯示 unfold
作業所產生的兩個序列範例,seq1
和 fib
。 第一個是 seq1
,只是一個最多 20 個數字的簡單序列。 第二個是 fib
,會使用 unfold
來計算 Fibonacci 序列。 因為 Fibonacci 序列中的每個元素都是前兩個 Fibonacci 數字的總和,所以狀態值是包含序列中前兩個數字的元組。 初始值為 (0,1)
,序列中的前兩個數字。
let seq1 =
0 // Initial state
|> Seq.unfold (fun state ->
if (state > 20) then
None
else
Some(state, state + 1))
printfn "The sequence seq1 contains numbers from 0 to 20."
for x in seq1 do
printf "%d " x
let fib =
(0, 1)
|> Seq.unfold (fun state ->
let cur, next = state
if cur < 0 then // overflow
None
else
let next' = cur + next
let state' = next, next'
Some (cur, state') )
printfn "\nThe sequence fib contains Fibonacci numbers."
for x in fib do printf "%d " x
輸出如下所示:
The sequence seq1 contains numbers from 0 to 20.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
The sequence fib contains Fibonacci numbers.
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
下列程式碼是使用這裡所述的許多序列模組函式,來產生和計算無限序列值的範例。 程式碼可能需要幾分鐘的時間執行。
// generateInfiniteSequence generates sequences of floating point
// numbers. The sequences generated are computed from the fDenominator
// function, which has the type (int -> float) and computes the
// denominator of each term in the sequence from the index of that
// term. The isAlternating parameter is true if the sequence has
// alternating signs.
let generateInfiniteSequence fDenominator isAlternating =
if (isAlternating) then
Seq.initInfinite (fun index ->
1.0 /(fDenominator index) * (if (index % 2 = 0) then -1.0 else 1.0))
else
Seq.initInfinite (fun index -> 1.0 /(fDenominator index))
// The harmonic alternating series is like the harmonic series
// except that it has alternating signs.
let harmonicAlternatingSeries = generateInfiniteSequence (fun index -> float index) true
// This is the series of reciprocals of the odd numbers.
let oddNumberSeries = generateInfiniteSequence (fun index -> float (2 * index - 1)) true
// This is the series of recipocals of the squares.
let squaresSeries = generateInfiniteSequence (fun index -> float (index * index)) false
// This function sums a sequence, up to the specified number of terms.
let sumSeq length sequence =
(0, 0.0)
|>
Seq.unfold (fun state ->
let subtotal = snd state + Seq.item (fst state + 1) sequence
if (fst state >= length) then
None
else
Some(subtotal, (fst state + 1, subtotal)))
// This function sums an infinite sequence up to a given value
// for the difference (epsilon) between subsequent terms,
// up to a maximum number of terms, whichever is reached first.
let infiniteSum infiniteSeq epsilon maxIteration =
infiniteSeq
|> sumSeq maxIteration
|> Seq.pairwise
|> Seq.takeWhile (fun elem -> abs (snd elem - fst elem) > epsilon)
|> List.ofSeq
|> List.rev
|> List.head
|> snd
// Compute the sums for three sequences that converge, and compare
// the sums to the expected theoretical values.
let result1 = infiniteSum harmonicAlternatingSeries 0.00001 100000
printfn "Result: %f ln2: %f" result1 (log 2.0)
let pi = Math.PI
let result2 = infiniteSum oddNumberSeries 0.00001 10000
printfn "Result: %f pi/4: %f" result2 (pi/4.0)
// Because this is not an alternating series, a much smaller epsilon
// value and more terms are needed to obtain an accurate result.
let result3 = infiniteSum squaresSeries 0.0000001 1000000
printfn "Result: %f pi*pi/6: %f" result3 (pi*pi/6.0)
搜尋和尋找元素
清單可用的序列支援功能:Seq.exists、Seq.exists2、Seq.find、Seq.findIndex、Seq.pick、Seq.tryFind 和 Seq.tryFindIndex。 這些可供序列使用的函式版本只會對序列評估搜尋的元素。 如需範例,請參閱清單。
取得子序列
Seq.filter 和 Seq.choose 就像適用於清單的對應函式,不同之處在於評估序列元素之前,篩選和選擇不會發生。
Seq.truncate 會從另一個序列建立序列,但會將序列限制為指定的元素數目。 Seq.take 會建立新的序列,該序列只包含序列開頭的指定元素數目。 如果序列中的元素少於您指定採用的元素,則 Seq.take
會擲回 System.InvalidOperationException
。 Seq.take
與 Seq.truncate
之間的差異,在於如果元素數目小於您指定的數目,則 Seq.truncate
不會產生錯誤。
下列程式碼顯示 Seq.truncate
與 Seq.take
之間的行為和差異。
let mySeq = seq { for i in 1 .. 10 -> i*i }
let truncatedSeq = Seq.truncate 5 mySeq
let takenSeq = Seq.take 5 mySeq
let truncatedSeq2 = Seq.truncate 20 mySeq
let takenSeq2 = Seq.take 20 mySeq
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
// Up to this point, the sequences are not evaluated.
// The following code causes the sequences to be evaluated.
truncatedSeq |> printSeq
truncatedSeq2 |> printSeq
takenSeq |> printSeq
// The following line produces a run-time error (in printSeq):
takenSeq2 |> printSeq
發生錯誤之前的輸出如下所示。
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
藉由使用 Seq.takeWhile,您可以指定述詞函式 (布林值函式),並從由述詞為 true
的這些原始序列元素所組成的其他序列建立序列,但在述詞傳回 false
的第一個元素之前停止。 Seq.skip 會傳回序列,略過另一個序列中第一個元素的指定數目,並傳回其餘元素。 Seq.skipWhile 會傳回一個序列,只要述詞傳回 true
,就會略過另一個序列的第一個元素,然後傳回其餘元素,從述詞傳回 false
的第一個元素開始。
下列程式碼範例說明 Seq.takeWhile
、Seq.skip
與 Seq.skipWhile
之間的行為和差異。
// takeWhile
let mySeqLessThan10 = Seq.takeWhile (fun elem -> elem < 10) mySeq
mySeqLessThan10 |> printSeq
// skip
let mySeqSkipFirst5 = Seq.skip 5 mySeq
mySeqSkipFirst5 |> printSeq
// skipWhile
let mySeqSkipWhileLessThan10 = Seq.skipWhile (fun elem -> elem < 10) mySeq
mySeqSkipWhileLessThan10 |> printSeq
輸出如下。
1 4 9
36 49 64 81 100
16 25 36 49 64 81 100
轉換序列
Seq.pairwise 會建立新的序列,其中輸入序列的後續元素會分組為元組。
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
let seqPairwise = Seq.pairwise (seq { for i in 1 .. 10 -> i*i })
printSeq seqPairwise
printfn ""
let seqDelta = Seq.map (fun elem -> snd elem - fst elem) seqPairwise
printSeq seqDelta
Seq.windowed 就像是 Seq.pairwise
,不同之處在於不會產生元組序列,而是會產生陣列序列,其中包含序列的相鄰元素複本 (視窗)。 您可以指定每個陣列中想要的相鄰元素數目。
下列程式碼範例示範 Seq.windowed
的用法。 在此案例中,視窗中的元素數目為 3。 此範例會使用 printSeq
,在上一個程式碼範例中定義。
let seqNumbers = [ 1.0; 1.5; 2.0; 1.5; 1.0; 1.5 ] :> seq<float>
let seqWindows = Seq.windowed 3 seqNumbers
let seqMovingAverage = Seq.map Array.average seqWindows
printfn "Initial sequence: "
printSeq seqNumbers
printfn "\nWindows of length 3: "
printSeq seqWindows
printfn "\nMoving average: "
printSeq seqMovingAverage
輸出如下。
初始序列:
1.0 1.5 2.0 1.5 1.0 1.5
Windows of length 3:
[|1.0; 1.5; 2.0|] [|1.5; 2.0; 1.5|] [|2.0; 1.5; 1.0|] [|1.5; 1.0; 1.5|]
Moving average:
1.5 1.666666667 1.5 1.333333333
具有多個序列的作業
Seq.zip 和 Seq.zip3 會採用兩個或三個序列,並產生元組序列。 這些函式就像適用於清單的對應函式。 沒有對應的功能,無法將一個序列分成兩個或多個序列。 如果您需要序列的這項功能,請將序列轉換成清單,並使用 List.unzip。
排序、比較和群組
清單支援的排序函式也適用於序列。 這包括 Seq.sort 和 Seq.sortBy。 這些函式會逐一查看整個序列。
您可以使用 Seq.compareWith 函式來比較兩個序列。 函式會依序比較後續元素,並在遇到第一個不相等的配對時停止。 任何其他元素都不會參與比較。
下列程式碼示範 Seq.compareWith
的用法。
let sequence1 = seq { 1 .. 10 }
let sequence2 = seq { 10 .. -1 .. 1 }
// Compare two sequences element by element.
let compareSequences =
Seq.compareWith (fun elem1 elem2 ->
if elem1 > elem2 then 1
elif elem1 < elem2 then -1
else 0)
let compareResult1 = compareSequences sequence1 sequence2
match compareResult1 with
| 1 -> printfn "Sequence1 is greater than sequence2."
| -1 -> printfn "Sequence1 is less than sequence2."
| 0 -> printfn "Sequence1 is equal to sequence2."
| _ -> failwith("Invalid comparison result.")
在先前的程式碼中,只會計算並檢查第一個元素,結果為 -1。
Seq.countBy 會採用一個函式,為每個元素產生稱為索引鍵的值。 藉由在每個元素上呼叫此函式,為每個元素產生索引鍵。 Seq.countBy
接著會傳回包含索引鍵值的序列,以及產生每個索引鍵值的元素數目計數。
let mySeq1 = seq { 1.. 100 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
let seqResult =
mySeq1
|> Seq.countBy (fun elem ->
if elem % 3 = 0 then 0
elif elem % 3 = 1 then 1
else 2)
printSeq seqResult
輸出如下。
(1, 34) (2, 33) (0, 33)
上一個輸出顯示原始序列有產生索引鍵 1 的 34 個元素、產生索引鍵 2 的 33 個值,以及產生索引鍵 0 的 33 個值。
您可以呼叫 Seq.groupBy 來分組序列的元素。 Seq.groupBy
會採用序列和函式,該函式會從元素產生索引鍵。 函式會在序列的每個元素上執行。 Seq.groupBy
會傳回元組序列,其中每個元組的第一個元素是索引鍵,而第二個則是產生該索引鍵的元素序列。
下列程式碼範例示範使用 Seq.groupBy
,將從 1 到 100 的數字序列分成三個群組,這些群組具有相異索引鍵值 0、1 和 2。
let sequence = seq { 1 .. 100 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
let sequences3 =
sequences
|> Seq.groupBy (fun index ->
if (index % 3 = 0) then 0
elif (index % 3 = 1) then 1
else 2)
sequences3 |> printSeq
輸出如下。
(1, seq [1; 4; 7; 10; ...]) (2, seq [2; 5; 8; 11; ...]) (0, seq [3; 6; 9; 12; ...])
您可以藉由呼叫 Seq.distinct 來建立可消除重複元素的序列。 或者,您可以使用 Seq.distinctBy,其會在每個元素上呼叫索引鍵產生函式。 產生的序列包含具有唯一索引鍵的原始序列元素;會捨棄產生先前元素重複索引鍵的後續元素。
下列程式碼範例會說明使用 Seq.distinct
。 藉由產生代表二進位數字的序列,然後顯示唯一的相異元素是 0 和 1,來示範 Seq.distinct
。
let binary n =
let rec generateBinary n =
if (n / 2 = 0) then [n]
else (n % 2) :: generateBinary (n / 2)
generateBinary n
|> List.rev
|> Seq.ofList
printfn "%A" (binary 1024)
let resultSequence = Seq.distinct (binary 1024)
printfn "%A" resultSequence
下列程式碼示範 Seq.distinctBy
,從包含負數和正數的序列開始,並使用絕對值函式作為索引鍵產生函式。 產生的序列遺漏對應至序列中負數的所有正數,因為負數稍早出現在序列中,因此會選取,而不是具有相同絕對值或索引鍵的正數。
let inputSequence = { -5 .. 10 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
printfn "Original sequence: "
printSeq inputSequence
printfn "\nSequence with distinct absolute values: "
let seqDistinctAbsoluteValue = Seq.distinctBy (fun elem -> abs elem) inputSequence
printSeq seqDistinctAbsoluteValue
唯讀和快取序列
Seq.readonly 會建立序列的唯讀複本。 當您有讀寫集合,例如陣列,而且您不想修改原始集合時,Seq.readonly
很有用。 此函式可用來保留資料封裝。 在下列程式碼範例中,會建立包含陣列的型別。 屬性會公開陣列,但不會傳回陣列,而是傳回使用 Seq.readonly
從陣列建立的序列。
type ArrayContainer(start, finish) =
let internalArray = [| start .. finish |]
member this.RangeSeq = Seq.readonly internalArray
member this.RangeArray = internalArray
let newArray = new ArrayContainer(1, 10)
let rangeSeq = newArray.RangeSeq
let rangeArray = newArray.RangeArray
// These lines produce an error:
//let myArray = rangeSeq :> int array
//myArray[0] <- 0
// The following line does not produce an error.
// It does not preserve encapsulation.
rangeArray[0] <- 0
Seq.cache 會建立序列的預存版本。 使用 Seq.cache
以避免重新評估序列,或當您有多個使用序列的執行緒時,但您必須確定每個元素只會執行一次動作。 當您有多個執行緒所使用的序列時,可以有一個執行緒來列舉和計算原始序列的值,而其餘執行緒可以使用快取序列。
在序列上執行計算
簡單的算數運算就像清單的運算,例如 Seq.average、Seq.sum、Seq.averageBy、Seq.sumBy 等等。
Seq.fold、Seq.reduce 和 Seq.scan 就像適用於清單的對應函式一樣。 序列支援列出支援之這些函式完整變化的子集。 如需詳細資訊與範例,請參閱清單。