シーケンス (F#)
シーケンスは、すべてが 1 つの型である論理的な一連の要素です。シーケンスは、順序付けられた大きいデータのコレクションを使用するものの、必ずしもそのすべての要素を使用するとは限らない場合に、特に便利です。シーケンスの個々の要素は必要な場合にのみ計算されるため、すべての要素を使用するとは限らない場合は、リストではなく、シーケンスを使用する方が高いパフォーマンスが得られます。シーケンスは seq<'T> 型で表されます。これは IEnumerable<T> のエイリアスです。したがって、System.IEnumerable を実装する任意の .NET Framework 型をシーケンスとして使用できます。Seq モジュールは、シーケンスに関する操作をサポートします。
シーケンス式
シーケンス式とは、シーケンスとして評価される式です。シーケンス式には複数の形式があります。最も簡単な形式は、範囲指定です。たとえば、seq { 1 .. 5 } は、両端の 1 と 5 を含む 5 つの要素で構成されるシーケンスを作成します。2 つの二重ピリオドの間にインクリメント値 (またはデクリメント値) を指定することもできます。たとえば、次のコードでは、10 の倍数のシーケンスが作成されます。
// Sequence that has an increment.
seq { 0 .. 10 .. 100 }
シーケンス式は、シーケンスの値を生成する F# 式で構成されます。F# 式では、yield キーワードを使用して、シーケンスの一部となる値を生成できます。
例を次に示します。
seq { for i in 1 .. 10 do yield i * i }
-> 演算子を yield の代わりに使用できます。その場合は、次の例で示すように do キーワードを省略できます。
seq { for i in 1 .. 10 -> i * i }
次のコードでは、グリッドを表す配列に、座標ペアの一覧がインデックスと共に生成されます。
let (height, width) = (10, 10)
seq { for row in 0 .. width - 1 do
for col in 0 .. height - 1 do
yield (row, col, row*width + col)
}
シーケンスで使用される if 式はフィルターです。たとえば、int -> bool 型の関数 isprime を使用する場合に、素数のみのシーケンスを生成するには、次のようにシーケンスを構築します。
seq { for n in 1 .. 100 do if isprime n then yield n }
反復処理で yield または -> を使用する場合は、各反復処理でシーケンスの単一の要素を生成する必要があります。各反復処理で複数の要素から成るシーケンスを生成する場合は、yield! を使用します。この場合、各反復処理で生成された要素を連結して、最終的なシーケンスを生成します。
シーケンス式では、複数の式を組み合わせることができます。各式によって生成された要素が 1 つに連結されます。例については、このトピックの「例」を参照してください。
例
最初の例では、反復処理、フィルター、および 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 yield n }
for x in aSequence do
printfn "%d" x
次のコードでは、yield を使用して、乗算テーブルを作成します。このテーブルは、3 つの要素 (2 つの因数とそれらの積) のタプルで構成されます。
let multiplicationTable =
seq { for i in 1..9 do
for j in 1..9 do
yield (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
シーケンスの使用
シーケンスは、リストと同じ関数の多くをサポートします。また、シーケンスは、キー生成関数を使用することでグループ化やカウントなどの操作もサポートします。さらに、サブシーケンスを抽出するためのさまざまな関数もサポートします。
リスト、配列、セット、マップなどの多くのデータ型は、列挙可能なコレクションであるため、暗黙のシーケンスです。引数としてシーケンスを受け取る関数では、IEnumerable<T> を実装する .NET Framework のデータ型に加えて、F# の一般的なデータ型も使用できます。これに対し、引数としてリストを受け取る関数では、リストのみを使用できます。seq<'a> 型は、IEnumerable<'a> の型略称です。つまり、F# の配列、リスト、セット、マップ、さらに、.NET Framework のほとんどのコレクション型など、ジェネリックな IEnumerable<T> を実装するすべての型は、seq 型と互換性があり、シーケンスが予期されるすべての場所で使用できます。
モジュール関数
Microsoft.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> 関数 (F#) を使用することで、配列およびリストからシーケンスを作成できます。または、キャスト演算子を使用して配列やリストをシーケンスに変換することもできます。両方の方法を次のコードに示します。
// 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 で定義されたコレクションなど) からシーケンスを作成できます。このような厳密に型指定されていないコレクションは、要素型が Object で、非ジェネリックな IEnumerable<T> 型を使用して列挙されます。次のコードでは、Seq.cast を使用して ArrayList をシーケンスに変換する方法を示します。
open System
let mutable arrayList1 = new System.Collections.ArrayList(10)
for i in 1 .. 10 do arrayList1.Add(10) |> ignore
let seqCast : seq<int> = Seq.cast arrayList1
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 値を返すことでシーケンスを終了できます。次に示す 2 つのコード seq1 と fib は、unfold 操作によって生成されるシーケンスの例です。最初の seq1 は、100 までの値から成る単純なシーケンスです。2 番目の fib では、unfold を使用してフィボナッチ数列を計算しています。フィボナッチ数列の各要素は前の 2 つのフィボナッチ数の合計であるため、状態値は、シーケンスの前の 2 つの値で構成されるタプルです。初期値は (1,1) で、シーケンスの最初の 2 つの値です。
let seq1 = Seq.unfold (fun state -> if (state > 20) then None else Some(state, state + 1)) 0
printfn "The sequence seq1 contains numbers from 0 to 20."
for x in seq1 do printf "%d " x
let fib = Seq.unfold (fun state ->
if (snd state > 1000) then None
else Some(fst state + snd state, (snd state, fst state + snd state))) (1,1)
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
次のコードは、ここで説明されているシーケンス モジュール関数の多くを使用して、無限シーケンスの値を生成および計算する例です。このコードの実行には数分かかることがあります。
// infiniteSequences.fs
// 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 series is the series of reciprocals of whole numbers.
let harmonicSeries = generateInfiniteSequence (fun index -> float index) false
// 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 =
Seq.unfold (fun state ->
let subtotal = snd state + Seq.nth (fst state + 1) sequence
if (fst state >= length) then None
else Some(subtotal,(fst state + 1, subtotal))) (0, 0.0)
// 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 は 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
出力は次のとおりです。
Initial sequence:
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 は、2 つまたは 3 つのシーケンスを受け取って、タプルのシーケンスを生成します。これらの関数は、リスト用の対応する関数と似ています。1 つのシーケンスを 2 つ以上のシーケンスに分割するための対応する機能はありません。シーケンスでこの機能が必要な場合は、シーケンスをリストに変換してから、List.unzip を使用します。
並べ替え、比較、グループ化
リスト用の並べ替え関数をシーケンスにも使用できます。これには、Seq.sort と Seq.sortBy が含まれます。これらの関数はシーケンス全体を反復処理します。
2 つのシーケンスを比較するには、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; printfn ""
let seqResult = Seq.countBy (fun elem -> if elem % 3 = 0 then 0
elif elem % 3 = 1 then 1
else 2) mySeq1
printSeq seqResult
出力は次のとおりです。
(1, 34) (2, 33) (0, 33)
この出力は、元のシーケンスの 34 個の要素がキー 1 を生成し、33 個の要素がキー 2 を生成し、33 個の要素がキー 0 を生成したことを示します。
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; printfn ""
let sequences3 = Seq.groupBy (fun index ->
if (index % 3 = 0) then 0
elif (index % 3 = 1) then 1
else 2) sequence
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 を使用して異なる要素が 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 ""
printfn "Original sequence: "
printSeq inputSequence
printfn "\nSequence with distinct absolute values: "
let seqDistinctAbsoluteValue = Seq.distinctBy (fun elem -> abs elem) inputSequence
seqDistinctAbsoluteValue |> printSeq
読み取り専用シーケンスとキャッシュ シーケンス
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 は、シーケンスの保存バージョンを作成します。1 つのシーケンスを使用する複数のスレッドがあり、各要素が確実に 1 回だけ処理されるようにする必要がある場合は、Seq.cache を使用してシーケンスが再評価されないようにします。複数のスレッドで使用されるシーケンスがあるときは、1 つのスレッドで元のシーケンスの値を列挙して計算し、他のスレッドはキャッシュされたシーケンスを使用することができます。
シーケンスに対する計算の実行
Seq.average、Seq.sum、Seq.averageBy、Seq.sumBy など、簡単な算術演算は、リストと同じように行うことができます。
Seq.fold、Seq.reduce、および Seq.scan は、リストに対して使用可能な対応する関数と同様です。シーケンスは、リストがサポートするこれらの関数の全バリエーションのサブセットをサポートします。使用例を含む詳細については、「リスト (F#)」を参照してください。