リスト (F#)
F# のリストは、順序が指定された、変更できない一連の要素です。これらの要素は同じ型です。
リストの作成と初期化
リストを定義するには、次のコード行で示すように、セミコロンで区切って明示的に列記した要素を角かっこで囲みます。
let list123 = [ 1; 2; 3 ]
要素間に改行を挿入することもでき、その場合はセミコロンを省略できます。要素の初期化式が長い場合や、各要素にコメントを含める場合は、改行の構文を使用すると、コードが読みやすくなります。
let list123 = [
1
2
3 ]
通常、リストの要素はすべて同じデータ型である必要があります。例外として、要素が基本型として指定されているリストは、派生型の要素を含むことができます。したがって、次に示す例は、Button と CheckBox がどちらも Control から派生するため、リストとして認められます。
let myControlList : Control list = [ new Button(); new CheckBox() ]
また、次のコードで示すように、整数を範囲演算子 (..) で区切って示した範囲を使用して、リストの要素を定義することもできます。
let list1 = [ 1 .. 10 ]
また、次のコードのように、ループ構造を使用してリストを定義することもできます。
let listOfSquares = [ for i in 1 .. 10 -> i*i ]
空のリストは、間に何も含まない 1 組の角かっこで示します。
// An empty list.
let listEmpty = []
シーケンス式を使用してリストを作成することもできます。「シーケンス」の「シーケンス式」を参照してください。たとえば、次に示すコードでは、1 から 10 までの整数の 2 乗のリストを作成しています。
let squaresList = [ for i in 1 .. 10 -> i * i ]
リストの操作に使用する演算子
リストに要素をアタッチするには、:: (cons) 演算子を使用します。list1 が [2; 3; 4] の場合、次のコードによって作成される list2 は [100; 2; 3; 4] となります。
let list2 = 100 :: list1
互換性のある型を含むリストを連結するには、次のコードのように @ 演算子を使用します。list1 が [2; 3; 4] で、list2 が [100; 2; 3; 4 ] の場合、次のコードで作成される list3 は [2; 3; 4; 100; 2; 3; 4] となります。
let list3 = list1 @ list2
リストに対する操作を実行する関数は、List モジュールにあります。
F# のリストは変更できないため、変更操作を行うと、既存のリストが変更されるのではなく、新しいリストが生成されます。
F# のリストは、シングルリンク リストとして実装されます。つまり、リストの先頭だけにアクセスする操作は O(1) で、要素へのアクセスは O(n) になります。
プロパティ
リスト型では次のプロパティがサポートされています。
プロパティ |
種類 |
Description |
---|---|---|
'T |
最初の要素。 |
|
'T list |
該当する型の空のリストを返す静的プロパティ。 |
|
bool |
リストに要素がない場合は true。 |
|
'T |
指定したインデックスの要素 (0 から始まります)。 |
|
int |
要素の数。 |
|
'T list |
最初の要素を除いたリスト。 |
これらのプロパティを使用した例を、次にいくつか示します。
let list1 = [ 1; 2; 3 ]
// Properties
printfn "list1.IsEmpty is %b" (list1.IsEmpty)
printfn "list1.Length is %d" (list1.Length)
printfn "list1.Head is %d" (list1.Head)
printfn "list1.Tail.Head is %d" (list1.Tail.Head)
printfn "list1.Tail.Tail.Head is %d" (list1.Tail.Tail.Head)
printfn "list1.Item(1) is %d" (list1.Item(1))
リストの使用
リストを使用してプログラミングを行うと、少量のコードで複雑な操作を実行できます。ここでは、関数型プログラミングにとって重要なリストに対する一般的な操作について説明します。
リストを使用した再帰
リストは、再帰的なプログラミング技法に非常に適しています。たとえば、ある操作を、リストのすべての要素に対して実行する必要があるとします。この操作は、リストの先頭に対して処理を行った後にリストの後部 (元のリストの先頭にある要素を除いた要素で構成される、最初のリストより小さいリスト) を次の再帰レベルに戻すことで、再帰的に実行できます。
このような再帰関数を記述するには、パターン マッチで cons 演算子 (::) を使用します。これによって、リストの先頭を末尾から分離できます。
パターン マッチを使用した、リストに対する操作を実行する再帰関数の実装方法を次のコード例に示します。
let rec sum list =
match list with
| head :: tail -> head + sum tail
| [] -> 0
このコードは小さいリストでは問題なく動作しますが、リストが大きくなると、スタックがオーバーフローする可能性があります。次に示すコードは、再帰関数の処理では標準的な技法であるアキュムレータ引数を使用して、このコードを改善したものです。アキュムレータ引数を使用すると、関数の後部が再帰的になり、スタック領域を節約できます。
let sum list =
let rec loop list acc =
match list with
| head :: tail -> loop tail (acc + head)
| [] -> acc
loop list 0
RemoveAllMultiples 関数は、2 つのリストを受け取る再帰関数です。最初のリストは、倍数を削除する数値が格納されたリストで、もう 1 つのリストは、数値を削除する元のリストです。次の例のコードでは、この再帰関数を使用して、リストから素数以外をすべて削除します。その結果、素数のリストが残ります。
let IsPrimeMultipleTest n x =
x = n || x % n <> 0
let rec RemoveAllMultiples listn listx =
match listn with
| head :: tail -> RemoveAllMultiples tail (List.filter (IsPrimeMultipleTest head) listx)
| [] -> listx
let GetPrimesUpTo n =
let max = int (sqrt (float n))
RemoveAllMultiples [ 2 .. max ] [ 1 .. n ]
printfn "Primes Up To %d:\n %A" 100 (GetPrimesUpTo 100)
出力は次のとおりです。
Primes Up To 100:
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59; 61; 67; 71; 73; 79; 83; 89; 97]
モジュール関数
List モジュールには、リストの要素にアクセスする関数があります。先頭の要素には、最も迅速かつ簡単にアクセスできます。それには、Head プロパティまたはモジュール関数の List.head を使用します。リストの後部にアクセスするには、Tail プロパティまたは List.tail 関数を使用します。インデックスで要素を検索するには、List.nth 関数を使用します。List.nth はリストを走査します。したがって、これは O(n) です。コードで List.nth を頻繁に使用する場合は、リストの代わりに配列を使用すると、効果的である可能性があります。配列での要素のアクセスは O(1) です。
リストに対するブール演算
List.isEmpty 関数は、リストに要素があるかどうかを調べる関数です。
List.exists 関数は、リストの要素に対してブール値の評価を行い、条件を満たす場合に true を返します。List.exists2 も同様の関数ですが、この関数では、2 つのリストの一連の要素のペアを操作できます。
List.exists を使用したコードの例を次に示します。
// Use List.exists to determine whether there is an element of a list satisfies a given Boolean expression.
// containsNumber returns true if any of the elements of the supplied list match
// the supplied number.
let containsNumber number list = List.exists (fun elem -> elem = number) list
let list0to3 = [0 .. 3]
printfn "For list %A, contains zero is %b" list0to3 (containsNumber 0 list0to3)
出力は次のとおりです。
For list [0; 1; 2; 3], contains zero is true
次の例は、List.exists2 の使い方を示しています。
// Use List.exists2 to compare elements in two lists.
// isEqualElement returns true if any elements at the same position in two supplied
// lists match.
let isEqualElement list1 list2 = List.exists2 (fun elem1 elem2 -> elem1 = elem2) list1 list2
let list1to5 = [ 1 .. 5 ]
let list5to1 = [ 5 .. -1 .. 1 ]
if (isEqualElement list1to5 list5to1) then
printfn "Lists %A and %A have at least one equal element at the same position." list1to5 list5to1
else
printfn "Lists %A and %A do not have an equal element at the same position." list1to5 list5to1
出力は次のとおりです。
Lists [1; 2; 3; 4; 5] and [5; 4; 3; 2; 1] have at least one equal element at the same position.
リストのすべての要素が条件を満たすかどうかをテストする場合は、List.forall を使用します。
let isAllZeroes list = List.forall (fun elem -> elem = 0.0) list
printfn "%b" (isAllZeroes [0.0; 0.0])
printfn "%b" (isAllZeroes [0.0; 1.0])
出力は次のとおりです。
true
false
同様に、List.forall2 は、2 つのリストの対応する位置にあるすべての要素が、要素の各ペアに関係するブール式を満たすかどうかを調べます。
let listEqual list1 list2 = List.forall2 (fun elem1 elem2 -> elem1 = elem2) list1 list2
printfn "%b" (listEqual [0; 1; 2] [0; 1; 2])
printfn "%b" (listEqual [0; 0; 0] [0; 1; 0])
出力は次のとおりです。
true
false
リストに対する並べ替え操作
List.sort、List.sortBy、List.sortWith の各関数は、リストを並べ替えます。並べ替え関数では、これら 3 つの関数のどれを使用するかを判断します。List.sort は、既定の一般的な比較を使用します。一般的な比較は、汎用の比較関数に基づくグローバル演算子を使用して、値を比較します。この比較は、単純な数値型、タプル、レコード、判別共用体、リスト、配列、および IComparable を実装する任意の型など、広範な要素型で効率的に動作します。IComparable を実装する型の場合は、汎用的な比較で CompareTo 関数が使用されます。また、汎用的な比較は文字列にも使用できますが、カルチャに依存しない並べ替え順序が使用されます。関数型のようなサポートされない型には、汎用的な比較を使用できません。また、既定の汎用的な比較は、小さい構造の型の場合に最高のパフォーマンスを示します。比較と並べ替えが頻繁に必要な大きい構造の型の場合は、IComparable を実装し、CompareTo メソッドを効率的に実装することを考慮してください。
List.sortBy 関数は、並べ替え基準として使用される値を返す関数を受け取ります。また、List.sortWith 関数は、比較関数を引数として受け取ります。これら 2 つの関数は、比較をサポートしない型を使用するとき、またはカルチャを認識する文字列の場合のように複雑な比較セマンティクスを必要とする比較の場合に役立ちます。
次の例は List.sort の使い方を示しています。
let sortedList1 = List.sort [1; 4; 8; -2; 5]
printfn "%A" sortedList1
出力は次のとおりです。
[-2; 1; 4; 5; 8]
List.sortBy を使用した例を次に示します。
let sortedList2 = List.sortBy (fun elem -> abs elem) [1; 4; 8; -2; 5]
printfn "%A" sortedList2
出力は次のとおりです。
[1; -2; 4; 5; 8]
List.sortWith を使用した例を次に示します。この例では、カスタムの比較関数 compareWidgets を使用して、まず、カスタム型の 1 つのフィールドを比較し、最初のフィールドの値が同じである場合は、さらに別のフィールドを比較しています。
type Widget = { ID: int; Rev: int }
let compareWidgets widget1 widget2 =
if widget1.ID < widget2.ID then -1 else
if widget1.ID > widget2.ID then 1 else
if widget1.Rev < widget2.Rev then -1 else
if widget1.Rev > widget2.Rev then 1 else
0
let listToCompare = [
{ ID = 92; Rev = 1 }
{ ID = 110; Rev = 1 }
{ ID = 100; Rev = 5 }
{ ID = 100; Rev = 2 }
{ ID = 92; Rev = 1 }
]
let sortedWidgetList = List.sortWith compareWidgets listToCompare
printfn "%A" sortedWidgetList
出力は次のとおりです。
[{ID = 92;
Rev = 1;}; {ID = 92;
Rev = 1;}; {ID = 100;
Rev = 2;}; {ID = 100;
Rev = 5;}; {ID = 110;
Rev = 1;}]
リストに対する検索操作
リストに対するさまざまな検索操作がサポートされています。最も単純なのは List.find で、これを使用すると、指定した条件に一致する最初の要素を検索できます。
次のコード例では、List.find を使用して、5 で割り切れる最初の数をリストから検索する方法を示します。
let isDivisibleBy number elem = elem % number = 0
let result = List.find (isDivisibleBy 5) [ 1 .. 100 ]
printfn "%d " result
結果は 5 になります。
先に要素を変換する必要がある場合は、List.pick を呼び出します。これは、オプションを返す関数を受け取り、Some(x) を満たす最初のオプション値を検索します。List.pick は、要素を返す代わりに、結果 x を返します。一致する要素が見つからない場合、List.pick は KeyNotFoundException をスローします。List.pick の使用方法を次のコードに示します。
let valuesList = [ ("a", 1); ("b", 2); ("c", 3) ]
let resultPick = List.pick (fun elem ->
match elem with
| (value, 2) -> Some value
| _ -> None) valuesList
printfn "%A" resultPick
出力は次のとおりです。
"b"
もう 1 つの検索操作グループである List.tryFind 関数およびその関連関数は、オプション値を返します。List.tryFind 関数は、条件を満たす要素がリストにある場合は、その最初の要素を返します。条件を満たす要素がない場合は、オプション値 None を返します。この関数のバリエーションである List.tryFindIndex は、要素のインデックスがある場合に、要素自体ではなく、そのインデックスを返します。これらの関数を次のコードに示します。
let list1d = [1; 3; 7; 9; 11; 13; 15; 19; 22; 29; 36]
let isEven x = x % 2 = 0
match List.tryFind isEven list1d with
| Some value -> printfn "The first even value is %d." value
| None -> printfn "There is no even value in the list."
match List.tryFindIndex isEven list1d with
| Some value -> printfn "The first even value is at position %d." value
| None -> printfn "There is no even value in the list."
出力は次のとおりです。
The first even value is 22.
The first even value is at position 8.
リストに対する算術演算
List モジュールには、合計や平均などの一般的な算術演算が組み込まれています。List.sum を使用するには、リストの要素の型が + 演算子をサポートし、ゼロ値を備えていることも必要です。組み込みの数値型はすべてこの条件を満たしています。List.average を使用するには、剰余のない除算が要素の型によってサポートされている必要があります。そのため、整数型では使用できませんが、浮動小数点型では使用できます。List.sumBy 関数と List.averageBy 関数は、関数をパラメーターとして受け取り、その関数の結果を使って合計値または平均値を計算します。
次のコードは、List.sum、List.sumBy、および List.average の使用方法を示しています。
// Compute the sum of the first 10 integers by using List.sum.
let sum1 = List.sum [1 .. 10]
// Compute the sum of the squares of the elements of a list by using List.sumBy.
let sum2 = List.sumBy (fun elem -> elem*elem) [1 .. 10]
// Compute the average of the elements of a list by using List.average.
let avg1 = List.average [0.0; 1.0; 1.0; 2.0]
printfn "%f" avg1
出力は 1.000000 になります。
List.averageBy の使用方法を次のコードに示します。
let avg2 = List.averageBy (fun elem -> float elem) [1 .. 10]
printfn "%f" avg2
出力は 5.5 になります。
リストとタプル
タプルを含むリストは、zip 関数および unzip 関数で操作できます。これらの関数は、単一値の 2 つのリストを結合してタプルのリストを 1 つ生成したり、タプルの 1 つのリストを分割して単一の値のリストを 2 つ生成したりします。最も単純な List.zip 関数は、単一の要素から成る 2 つのリストを受け取り、タプルのペアで構成された 1 つのリストを生成します。もう 1 つのバージョンである List.zip3 は、単一の要素から成る 3 つのリストを受け取り、3 つの要素を持つタプルで構成された 1 つのリストを生成します。List.zip を使用したコード例を次に示します。
let list1 = [ 1; 2; 3 ]
let list2 = [ -1; -2; -3 ]
let listZip = List.zip list1 list2
printfn "%A" listZip
出力は次のとおりです。
[(1, -1); (2, -2); (3; -3)]
List.zip3 を使用したコード例を次に示します。
let list3 = [ 0; 0; 0]
let listZip3 = List.zip3 list1 list2 list3
printfn "%A" listZip3
出力は次のとおりです。
[(1, -1, 0); (2, -2, 0); (3, -3, 0)]
対応する unzip のバージョンである List.unzip および List.unzip3 は、タプルのリストを受け取り、タプルの形式のリストを返します。最初のリストには、各タプルの最初にあるすべての要素が含まれ、2 番目のリストには、各タプルの 2 番目にあるすべての要素が含まれます。
List.unzip の使用方法を示すコード例を次に示します。
let lists = List.unzip [(1,2); (3,4)]
printfn "%A" lists
printfn "%A %A" (fst lists) (snd lists)
出力は次のとおりです。
([1; 3], [2; 4])
[1; 3] [2; 4]
List.unzip3 の使用方法を示すコード例を次に示します。
let listsUnzip3 = List.unzip3 [(1,2,3); (4,5,6)]
printfn "%A" listsUnzip3
出力は次のとおりです。
([1; 4], [2; 5], [3; 6])
リストの要素に対する操作
F# は、リストの要素に対するさまざまな操作をサポートしています。最も単純なのは List.iter で、これを使用すると、リストのすべての要素に対する関数を呼び出すことができます。このバリエーションとしては、2 つのリストの要素に対して操作を実行できる List.iter2 があります。また、List.iter に似ているが、各要素に対して呼び出された関数に引数として各要素のインデックスを渡す List.iteri、および List.iter2 と List.iteri の機能を組み合わせた List.iteri2 があります。これらの関数を次のコード例に示します。
let list1 = [1; 2; 3]
let list2 = [4; 5; 6]
List.iter (fun x -> printfn "List.iter: element is %d" x) list1
List.iteri(fun i x -> printfn "List.iteri: element %d is %d" i x) list1
List.iter2 (fun x y -> printfn "List.iter2: elements are %d %d" x y) list1 list2
List.iteri2 (fun i x y ->
printfn "List.iteri2: element %d of list1 is %d element %d of list2 is %d"
i x i y)
list1 list2
出力は次のとおりです。
List.iter: element is 1
List.iter: element is 2
List.iter: element is 3
List.iteri: element 0 is 1
List.iteri: element 1 is 2
List.iteri: element 2 is 3
List.iter2: elements are 1 4
List.iter2: elements are 2 5
List.iter2: elements are 3 6
List.iteri2: element 0 of list1 is 1; element 0 of list2 is 4
List.iteri2: element 1 of list1 is 2; element 1 of list2 is 5
List.iteri2: element 2 of list1 is 3; element 2 of list2 is 6
また、リストの要素の変換によく使用されるもう 1 つの関数 List.map では、リストの各要素に関数を適用し、すべての結果を含む新しいリストを生成できます。List.map2 と List.map3 は、複数のリストを受け取るバリエーションです。また、要素に加えて、各要素のインデックスを関数に渡す必要がある場合は、List.mapi および List.mapi2 も使用できます。List.mapi2 と List.mapi の違いは、List.mapi2 が 2 つのリストを使用する点のみです。次に List.map の例を示します。
let list1 = [1; 2; 3]
let newList = List.map (fun x -> x + 1) list1
printfn "%A" newList
出力は次のとおりです。
[2; 3; 4]
List.map2 を使用する例を次に示します。
let list1 = [1; 2; 3]
let list2 = [4; 5; 6]
let sumList = List.map2 (fun x y -> x + y) list1 list2
printfn "%A" sumList
出力は次のとおりです。
[5; 7; 9]
List.map3 を使用する例を次に示します。
let newList2 = List.map3 (fun x y z -> x + y + z) list1 list2 [2; 3; 4]
printfn "%A" newList2
出力は次のとおりです。
[7; 10; 13]
List.mapi を使用する例を次に示します。
let newListAddIndex = List.mapi (fun i x -> x + i) list1
printfn "%A" newListAddIndex
出力は次のとおりです。
[1; 3; 5]
List.mapi2 を使用する例を次に示します。
let listAddTimesIndex = List.mapi2 (fun i x y -> (x + y) * i) list1 list2
printfn "%A" listAddTimesIndex
出力は次のとおりです。
[0; 7; 18]
List.collect は、各要素が生成するリストがすべて連結されて最終的に 1 つのリストになる点を除き、List.map と似ています。次のコードでは、リストの各要素が 3 つの値を生成します。これらすべてが 1 つのリストに集約されます。
let collectList = List.collect (fun x -> [for i in 1..3 -> x * i]) list1
printfn "%A" collectList
出力は次のとおりです。
[1; 2; 3; 2; 4; 6; 3; 6; 9]
さらに、ブール条件を受け取り、指定された条件を満たす要素のみで構成される新しいリストを生成する List.filter という関数も使用できます。
let evenOnlyList = List.filter (fun x -> x % 2 = 0) [1; 2; 3; 4; 5; 6]
結果のリストは [2; 4; 6] です。
map と filter を組み合わせた List.choose を使用すると、要素の変換と選択を一度に行うことができます。List.choose は、オプションを返す関数をリストの各要素に適用し、関数がオプション値 Some を返す要素の結果から成る新しいリストを返します。
次のコードでは、List.choose を使用して、最初の文字が大文字の単語を単語のリストから選択しています。
let listWords = [ "and"; "Rome"; "Bob"; "apple"; "zebra" ]
let isCapitalized (string1:string) = System.Char.IsUpper string1.[0]
let results = List.choose (fun elem ->
match elem with
| elem when isCapitalized elem -> Some(elem + "'s")
| _ -> None) listWords
printfn "%A" results
出力は次のとおりです。
["Rome's"; "Bob's"]
複数のリストに対する操作
複数のリストを 1 つに結合することができます。2 つのリストを 1 つに結合するには、List.append を使用します。3 つ以上のリストを結合するには、List.concat を使用します。
let list1to10 = List.append [1; 2; 3] [4; 5; 6; 7; 8; 9; 10]
let listResult = List.concat [ [1; 2; 3]; [4; 5; 6]; [7; 8; 9] ]
List.iter (fun elem -> printf "%d " elem) list1to10
printfn ""
List.iter (fun elem -> printf "%d " elem) listResult
フォールド操作とスキャン操作
リストの操作の中には、リストのすべての要素間の依存関係を伴うものがあります。フォールド操作とスキャン操作は、各要素に対して関数を呼び出す点で List.iter や List.map に似ていますが、これらの操作には、計算時の情報を保持する accumulator という名前の追加のパラメーターがあります。
リストに対して計算を実行するには、List.fold を使用します。
List.fold を使用してさまざまな操作を実行するコード例を次に示します。
リストが走査されます。アキュムレータ acc は、計算の進行と共に渡される値です。最初の引数はアキュムレータとリスト要素を受け取り、そのリスト要素に対する計算の中間結果を返します。2 番目の引数はアキュムレータの初期値です。
let sumList list = List.fold (fun acc elem -> acc + elem) 0 list
printfn "Sum of the elements of list %A is %d." [ 1 .. 3 ] (sumList [ 1 .. 3 ])
// The following example computes the average of a list.
let averageList list = (List.fold (fun acc elem -> acc + float elem) 0.0 list / float list.Length)
// The following example computes the standard deviation of a list.
// The standard deviation is computed by taking the square root of the
// sum of the variances, which are the differences between each value
// and the average.
let stdDevList list =
let avg = averageList list
sqrt (List.fold (fun acc elem -> acc + (float elem - avg) ** 2.0 ) 0.0 list / float list.Length)
let testList listTest =
printfn "List %A average: %f stddev: %f" listTest (averageList listTest) (stdDevList listTest)
testList [1; 1; 1]
testList [1; 2; 1]
testList [1; 2; 3]
// List.fold is the same as to List.iter when the accumulator is not used.
let printList list = List.fold (fun acc elem -> printfn "%A" elem) () list
printList [0.0; 1.0; 2.5; 5.1 ]
// The following example uses List.fold to reverse a list.
// The accumulator starts out as the empty list, and the function uses the cons operator
// to add each successive element to the head of the accumulator list, resulting in a
// reversed form of the list.
let reverseList list = List.fold (fun acc elem -> elem::acc) [] list
printfn "%A" (reverseList [1 .. 10])
関数名に数字が付いている関数は、複数のリストを操作するバージョンです。たとえば、List.fold2 は、2 つのリストに対して計算を実行します。
次の例は、List.fold2 の使い方を示しています。
// Use List.fold2 to perform computations over two lists (of equal size) at the same time.
// Example: Sum the greater element at each list position.
let sumGreatest list1 list2 = List.fold2 (fun acc elem1 elem2 ->
acc + max elem1 elem2) 0 list1 list2
let sum = sumGreatest [1; 2; 3] [3; 2; 1]
printfn "The sum of the greater of each pair of elements in the two lists is %d." sum
List.fold と List.scan の違いは、List.fold が追加のパラメーターの最終値を返すのに対し、List.scan は (最終値に加え) 追加のパラメーターの中間値のリストを返すことです。
これらの各関数には、リストの走査順序と引数の順序が逆であるバリエーションがあります (List.foldBack など)。また、List.fold と List.foldBack にはそれぞれ、同じ長さの 2 つのリストを受け取る List.fold2 および List.foldBack2 というバリエーションがあります。各要素に対して実行される関数では、両方のリストの対応する要素を使用して操作を実行できます。2 つのリストの要素の型が同じである必要はありません。たとえば、次の例では、一方のリストには銀行口座の取引金額が格納され、もう一方のリストには取引の種類 (預け入れまたは引き出し) が格納されています。
// Discriminated union type that encodes the transaction type.
type Transaction =
| Deposit
| Withdrawal
let transactionTypes = [Deposit; Deposit; Withdrawal]
let transactionAmounts = [100.00; 1000.00; 95.00 ]
let initialBalance = 200.00
// Use fold2 to perform a calculation on the list to update the account balance.
let endingBalance = List.fold2 (fun acc elem1 elem2 ->
match elem1 with
| Deposit -> acc + elem2
| Withdrawal -> acc - elem2)
initialBalance
transactionTypes
transactionAmounts
printfn "%f" endingBalance
合計のような計算の場合は、結果が走査の順序に依存しないため、List.fold と List.foldBack のどちらを使用しても、同じ結果になります。次の例では、List.foldBack を使用してリストの要素を追加しています。
let sumListBack list = List.foldBack (fun acc elem -> acc + elem) list 0
printfn "%d" (sumListBack [1; 2; 3])
// For a calculation in which the order of traversal is important, fold and foldBack have different
// results. For example, replacing fold with foldBack in the listReverse function
// produces a function that copies the list, rather than reversing it.
let copyList list = List.foldBack (fun elem acc -> elem::acc) list []
printfn "%A" (copyList [1 .. 10])
次の例では、また銀行口座の例に戻ります。今度は、利息を計算する新しい取引の種類が追加されています。この例の場合は、取引の順序によって最終的な残高が異なります。
type Transaction2 =
| Deposit
| Withdrawal
| Interest
let transactionTypes2 = [Deposit; Deposit; Withdrawal; Interest]
let transactionAmounts2 = [100.00; 1000.00; 95.00; 0.05 / 12.0 ]
let initialBalance2 = 200.00
// Because fold2 processes the lists by starting at the head element,
// the interest is calculated last, on the balance of 1205.00.
let endingBalance2 = List.fold2 (fun acc elem1 elem2 ->
match elem1 with
| Deposit -> acc + elem2
| Withdrawal -> acc - elem2
| Interest -> acc * (1.0 + elem2))
initialBalance2
transactionTypes2
transactionAmounts2
printfn "%f" endingBalance2
// Because foldBack2 processes the lists by starting at end of the list,
// the interest is calculated first, on the balance of only 200.00.
let endingBalance3 = List.foldBack2 (fun elem1 elem2 acc ->
match elem1 with
| Deposit -> acc + elem2
| Withdrawal -> acc - elem2
| Interest -> acc * (1.0 + elem2))
transactionTypes2
transactionAmounts2
initialBalance2
printfn "%f" endingBalance3
関数 List.reduce は、List.fold や List.scan に似ています。ただし、List.reduce は、別のアキュムレータを受け渡すのではなく、関数を受け取ります。この関数は要素の型の引数を、1 つだけでなく、2 つ受け取り、この引数の 1 つがアキュムレータとして機能して、計算の途中結果を保持します。List.reduce は、まず、初めに最初の 2 つのリスト要素に対して演算を実行し、次にその演算の結果と次の要素を合わせて使用します。独自の型を持つ別のアキュムレータがないため、List.reduce を List.fold の代わりに使用できるのは、アキュムレータと要素が同じ型を持つ場合だけです。List.reduce を使用したコードの例を次に示します。指定されたリストに要素がない場合、List.reduce は例外をスローします。
次のコードでは、ラムダ式の最初の呼び出しで引数 2 と 4 を受け取って 6 を返し、次の呼び出しで引数 6 と 10 を受け取るので、結果が 16 になります。
let sumAList list =
try
List.reduce (fun acc elem -> acc + elem) list
with
| :? System.ArgumentException as exc -> 0
let resultSum = sumAList [2; 4; 10]
printfn "%d " resultSum
リストと他のコレクション型との変換
List モジュールには、シーケンスと配列との間で両方向の変換を行うための関数が用意されています。シーケンスとの間で変換を行うには、List.toSeq または List.ofSeq を使用します。配列との間で変換を行うには、List.toArray または List.ofArray を使用します。
その他の操作
リストに対するその他の操作については、ライブラリ リファレンスのトピック「Collections.List モジュール (F#)」を参照してください。