序列

序列是一連串的邏輯元素,而所有元素的型別都相同。 當您有大型、已排序的資料集合,但不一定預期使用所有元素時,序列特別有用。 個別序列元素只會視需要計算,因此序列在未使用所有元素的情況下,可提供比清單更好的效能。 序列是以 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 產生從 1x 的所有值之外,還會產生 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.ofArraySeq.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&#96;1 型別來列舉。 下列程式碼說明如何使用 Seq.castSystem.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 作業所產生的兩個序列範例,seq1fib。 第一個是 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.existsSeq.exists2Seq.findSeq.findIndexSeq.pickSeq.tryFindSeq.tryFindIndex。 這些可供序列使用的函式版本只會對序列評估搜尋的元素。 如需範例,請參閱清單

取得子序列

Seq.filterSeq.choose 就像適用於清單的對應函式,不同之處在於評估序列元素之前,篩選和選擇不會發生。

Seq.truncate 會從另一個序列建立序列,但會將序列限制為指定的元素數目。 Seq.take 會建立新的序列,該序列只包含序列開頭的指定元素數目。 如果序列中的元素少於您指定採用的元素,則 Seq.take 會擲回 System.InvalidOperationExceptionSeq.takeSeq.truncate 之間的差異,在於如果元素數目小於您指定的數目,則 Seq.truncate 不會產生錯誤。

下列程式碼顯示 Seq.truncateSeq.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.takeWhileSeq.skipSeq.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.zipSeq.zip3 會採用兩個或三個序列,並產生元組序列。 這些函式就像適用於清單的對應函式。 沒有對應的功能,無法將一個序列分成兩個或多個序列。 如果您需要序列的這項功能,請將序列轉換成清單,並使用 List.unzip

排序、比較和群組

清單支援的排序函式也適用於序列。 這包括 Seq.sortSeq.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.averageSeq.sumSeq.averageBySeq.sumBy 等等。

Seq.foldSeq.reduceSeq.scan 就像適用於清單的對應函式一樣。 序列支援列出支援之這些函式完整變化的子集。 如需詳細資訊與範例,請參閱清單

另請參閱