序列

序列是所有一種型別的邏輯專案系列。 當您擁有大型、已排序的資料集合,但不一定預期使用所有元素時,序列特別有用。 個別的時序元素只會視需要計算,因此順序在未使用所有元素的情況下,可提供比清單更好的效能。 序列是以 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 }

上一個範例會 -> 使用 運算子,這可讓您指定運算式,其值會成為序列的一部分。 只有在後面程式碼的每個部分都會傳回值時,才能使用 ->

或者,您可以使用下列選擇性的 來 yield 指定 do 關鍵字:

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 -> boolisprime 式,請建構序列,如下所示。

seq { for n in 1 .. 100 do if isprime n then n }

如先前所述,這裡是必要的, do 因為 沒有 else 與 一起 if 執行的分支。 如果您嘗試使用 -> ,您會收到錯誤,指出並非所有分支都會傳回值。

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 產生 x 的值,以及從 1x 的所有值。

範例

第一個範例會使用包含反復專案、篩選準則和 yield 的序列運算式來產生陣列。 此程式碼會將 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.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.展開 會從計算函式產生序列,該函式會接受狀態並加以轉換,以在序列中產生每個後續元素。 狀態只是用來計算每個元素的值,而且可以在計算每個元素時變更。 的第二個引數 Seq.unfold 是用來啟動序列的初始值。 Seq.unfold 會使用 狀態的選項類型,這可讓您藉由傳回 None 值來終止序列。 下列程式碼顯示作業所產生的兩個序列範例 和 seq1fibunfold 第一 seq1 個是簡單的序列,最多 20 個數字。 第二個 fib , 會使用 unfold 來計算 Fibonacci 序列。 因為 Fibonacci 序列中的每個元素都是前兩個 Fibonacci 數位的總和,所以狀態值是一個 Tuple,其中包含序列中的前兩個數字。 初始值為 (1,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 =
    (1, 1) // Initial state
    |> Seq.unfold (fun state ->
        if (snd state > 1000) then
            None
        else
            Some(fst state + snd state, (snd state, fst state + snd 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.

2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

下列程式碼是一個範例,其使用這裡所述的許多序列模組函式來產生和計算無限序列的值。 執行程式碼可能需要幾分鐘的時間。

// 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.InvalidOperationException 擲回 。 和 Seq.truncate 之間的差異 Seq.take 在於 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,您可以指定述詞函式 (布耳函數) ,並從由述詞傳 truefalse 之原始序列的專案所組成的另一個序列建立序列。 Seq.skip 會傳回一個序列,略過另一個序列中第一個元素的指定數目,並傳回剩餘的專案。 Seq.skipWhile 會傳回一個序列,只要述詞傳回 true ,就會略過另一個序列的第一個專案,然後傳回剩餘的專案,從述詞傳回 false 的第一個元素開始。

下列程式碼範例說明 、 Seq.skipSeq.skipWhile 之間的 Seq.takeWhile 行為和 差異。

// 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 會建立新的序列,其中輸入序列的後續元素會分組為 Tuple。

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 是 ,不同之處在于它不會產生 Tuple 序列,而是會產生陣列序列,其中包含相鄰元素的複本, (序列中的視窗) 。 您可以指定每個陣列中想要的相鄰元素數目。

下列程式碼範例示範 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 會採用兩或三個序列,並產生 Tuple 序列。 這些函式 就像清單可用的對應函式。 沒有對應的功能,無法將一個序列分成兩個或多個序列。 如果您需要序列的這項功能,請將序列轉換成清單,並使用 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)

上一個輸出顯示原始序列有 34 個元素產生索引鍵 1、33 個產生索引鍵 2 的值,以及產生索引鍵 0 的 33 個值。

您可以呼叫 Seq.groupBy來分組序列的專案。 Seq.groupBy 會採用序列和函式,該函式會從專案產生索引鍵。 函式會在序列的每個元素上執行。 Seq.groupBy 會傳回 Tuple 序列,其中每個 Tuple 的第一個專案是索引鍵,而第二個則是產生該索引鍵的專案序列。

下列程式碼範例示範 如何使用 Seq.groupBy ,將數位序列從 1 分割為 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.distinctSeq.distinct 藉由產生代表二進位數的序列來示範,然後顯示唯一的相異元素是 0 和 1。

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.reduce 和 Seq.scan就像清單可用的對應函式一樣。 序列支援列出支援之這些函式完整變化的子集。 如需詳細資訊和範例,請參閱 清單

另請參閱