次の方法で共有


シーケンス

シーケンスは、1 つの型のすべての要素の論理系列です。 シーケンスは、大量の順序付けされたデータコレクションがあるが、必ずしもすべての要素を使用するとは限らない場合に特に便利です。 個々のシーケンス要素は必要に応じてのみ計算されるため、すべての要素が使用されるわけではない状況では、シーケンスの方がパフォーマンスが向上します。 シーケンスは、IEnumerable<T>のエイリアスであるseq<'T>型で表されます。 したがって、 IEnumerable<T> インターフェイスを実装する .NET 型は、シーケンスとして使用できます。 Seq モジュールは、シーケンスを含む操作のサポートを提供します。

シーケンス式

シーケンス式は、シーケンスに評価される式です。 シーケンス式は、さまざまな形式をとることができます。 最も単純な形式では、範囲を指定します。 たとえば、 seq { 1 .. 5 } は、エンドポイント 1 と 5 を含む 5 つの要素を含むシーケンスを作成します。 また、2 つの二重期間の間のインクリメント (またはデクリメント) を指定することもできます。 たとえば、次のコードは、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 -> bool型の関数isprimeがある場合は、次のようにシーケンスを構築します。

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

前述のように、ifに対応するelseブランチがないため、ここでdoが必要です。 ->を使用しようとすると、すべての分岐が値を返すわけではないというエラーが表示されます。

yield! キーワード

場合によっては、要素のシーケンスを別のシーケンスに含めることができます。 シーケンスを別のシーケンス内に含めるには、 yield! キーワードを使用する必要があります。

// Repeats '1 2 3 4 5' ten times
seq {
    for _ in 1..10 do
        yield! seq { 1; 2; 3; 4; 5}
}

yield!のもう 1 つの考え方は、内部シーケンスをフラット化し、それを含むシーケンスに含めるということです。

式で 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}
}

前の例では、各x1からxまでのすべての値に加えて、xの値が生成されます。

例示

最初の例では、反復処理、フィルター、および 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

次の例では、2 つの要素と積で構成される 3 つの要素のタプルで構成される乗算テーブルを作成します。

let multiplicationTable =
    seq {
        for i in 1..9 do
            for j in 1..9 -> (i, j, i * j)
    }

次の例では、 yield! を使用して個々のシーケンスを 1 つの最終的なシーケンスに結合する方法を示します。 この場合、バイナリ ツリー内の各サブツリーのシーケンスが再帰関数で連結され、最終的なシーケンスが生成されます。

// 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>の型の省略形です。 つまり、F# の配列、リスト、セット、およびマップを含むジェネリック System.Collections.Generic.IEnumerable<'T>を実装する型と、ほとんどの .NET コレクション型は、 seq 型と互換性があり、シーケンスが必要な場所で使用できます。

モジュール関数

FSharp.Collections 名前空間Seq モジュールには、シーケンスを操作するための関数が含まれています。 これらの関数は、リスト、配列、マップ、およびセットでも機能します。これらの型はすべて列挙可能であるため、シーケンスとして扱うことができます。

シーケンスの作成

シーケンスは、前に説明したようにシーケンス式を使用するか、特定の関数を使用して作成できます。

Seq.empty を使用して空のシーケンスを作成することも、Seq.singleton を使用して指定した要素のシーケンスを 1 つだけ作成することもできます。

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&#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 関数を使用して無限シーケンスを定義できます。 このようなシーケンスでは、要素のインデックスから各要素を生成する関数を指定します。 遅延評価のために無限シーケンスが可能です。要素は、指定した関数を呼び出すことによって必要に応じて作成されます。 次のコード例では、浮動小数点数の無限シーケンスを生成します。この場合、連続する整数の 2 乗の逆数の系列が交互に生成されます。

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する 2 番目の引数は、シーケンスの開始に使用される初期値です。 Seq.unfold は状態にオプション型を使用します。これにより、 None 値を返すことでシーケンスを終了できます。 次のコードは、unfold操作によって生成されるシーケンスの 2 つの例 (seq1fib) を示しています。 1 つ目の seq1は、20 までの数値を持つ単純なシーケンスです。 2 つ目の fibでは、 unfold を使用してフィボナッチ シーケンスを計算します。 フィボナッチ配列の各要素は前の 2 つのフィボナッチ数の合計であるため、状態値はシーケンス内の前の 2 つの数値で構成されるタプルです。 初期値は (0,1)で、シーケンス内の最初の 2 つの数値です。

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.findIndexSeq.pickSeq.tryFindSeq.tryFindIndex の各リストで使用できる機能をサポートします。 シーケンスに使用できるこれらの関数のバージョンでは、検索対象の要素までのみシーケンスが評価されます。 例については、「 リスト」を参照してください。

サブシーケンスの取得

Seq.filterSeq.choose は、リストで使用できる対応する関数に似ていますが、シーケンス要素が評価されるまでフィルター処理と選択は行われません。

Seq.truncate は、別のシーケンスからシーケンスを作成しますが、シーケンスを指定された数の要素に制限します。 Seq.take は、シーケンスの先頭から指定された数の要素のみを含む新しいシーケンスを作成します。 シーケンス内の要素の数が指定した数よりも少ない場合、 Seq.takeSystem.InvalidOperationExceptionをスローします。 Seq.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.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.windowedSeq.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 は 、2 つまたは 3 つのシーケンスを受け取り、タプルのシーケンスを生成します。 これらの関数は、 リストで使用できる対応する関数に似ています。 1 つのシーケンスを 2 つ以上のシーケンスに分割するための対応する機能はありません。 シーケンスにこの機能が必要な場合は、シーケンスをリストに変換し、 List.unzip を使用します。

並べ替え、比較、およびグループ化

リストでサポートされている並べ替え関数は、シーケンスでも機能します。 これには 、Seq.sortSeq.sortBy が含まれます。 これらの関数は、シーケンス全体を反復処理します。

Seq.compareWith 関数を使用して、2 つのシーケンスを比較します。 この関数は連続する要素を順番に比較し、最初の等しくないペアが検出されると停止します。 追加の要素は比較には影響しません。

次のコードは、 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 はタプルのシーケンスを返します。各タプルの最初の要素がキーであり、2 番目の要素がそのキーを生成する要素のシーケンスです。

次のコード例は、 Seq.groupBy を使用して、1 から 100 までの数値のシーケンスを、個別のキー値 0、1、2 を持つ 3 つのグループにパーティション分割する方法を示しています。

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の使用方法を示しています。 Seq.distinct は、2 進数を表すシーケンスを生成し、個別の要素が 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 シーケンス

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 を使用するか、シーケンスを使用するスレッドが複数ある場合に、各要素が 1 回だけ処理されるようにする必要があります。 複数のスレッドで使用されているシーケンスがある場合は、元のシーケンスの値を列挙して計算する 1 つのスレッドを作成でき、残りのスレッドはキャッシュされたシーケンスを使用できます。

シーケンスに対する計算の実行

単純な算術演算は、Seq.average、Seq.sumSeq.averageBy、Seq.sumBy などのリストのようなものです。

Seq.foldSeq.reduceSeq.scan は、リストで使用できる対応する関数に似ています。 シーケンスは、サポートを一覧表示するこれらの関数の完全なバリエーションのサブセットをサポートします。 詳細と例については、「 リスト」を参照してください。

こちらも参照ください