共用方式為


序列

序列是一系列邏輯專案,全部都是一種類型。 當您擁有大量已排序的數據集合,但不一定預期使用所有元素時,序列特別有用。 個別序列元素僅視需要計算,因此序列可提供比清單更好的效能,但並非所有專案都使用。 序列是由 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序列中使用的表達式是篩選條件。 例如,若要只產生質數序列,假設您有 isprime 類型的 int -> bool函式,請建構序列,如下所示。

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 的值1xx

範例

第一個範例會使用包含反覆專案、篩選條件和 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

下列範例會建立乘法數據表,其中包含三個元素的 Tuple,每個元素都包含兩個因素和產品:

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

使用序列

序列支援許多與 清單相同的函式。 序列也支援使用索引鍵產生函式來分組和計數等作業。 序列也支援更多樣化的函式來擷取子序列。

許多數據類型,例如清單、陣列、集合和對應都是隱含序列,因為它們是可列舉的集合。 除了實作 的任何 .NET System.Collections.Generic.IEnumerable<'T>數據類型之外,採用序列做為自變數的函式也適用於任何通用 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.unfold 會從計算函式產生序列,該函式會採用狀態並轉換它,以產生序列中的每個後續專案。 狀態只是用來計算每個元素的值,而且可以在計算每個元素時變更。 的第二個自變數 Seq.unfold 是用來啟動序列的初始值。 Seq.unfold 會使用狀態的選項類型,這可讓您傳回 None 值來終止序列。 下列程式代碼顯示作業所產生的序列和fib的兩個unfold範例seq1。 第一個, 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 =
    sequence
    |> Seq.skip 1                      // skip first item (matching the original behavior)
    |> Seq.truncate length             // don't take more than length items
    |> Seq.scan (+) 0.0                // generate running sums
    |> Seq.skip 1                      // skip the initial 0.0 from sequence of running sums

// 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.exists2Seq.findSeq.findIndexSeq.pickSeq.tryFind 和 Seq.tryFindIndex 序列可用的這些函式版本只會評估要搜尋的專案。 如需範例,請參閱 清單

取得 Subsequences

Seq.filterSeq.choose 就像清單可用的對應函式,不同之處在於篩選和選擇不會發生,直到評估時序元素為止。

Seq.truncate 會從另一個序列建立序列,但會將序列限制為指定的項目數目。 Seq.take 會建立新的序列,其中包含序列開頭的指定項目數目。 如果序列中的元素少於您指定的接受專案, Seq.take 則會 System.InvalidOperationException擲回 。 和 Seq.truncate 之間的差異Seq.take在於,Seq.truncate如果元素數目小於您指定的數位,則不會產生錯誤。

下列程式代碼顯示和Seq.take之間的Seq.truncate行為和差異。

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.skip和之間的Seq.takeWhile行為和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 會建立新的序列,其中輸入序列的後續元素會分組為 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

Readonly 和 Cached Sequences

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.sum、Seq.averageBy、Seq.sumBy 等等

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

另請參閱