F# List基礎問題精講
この文書は、F#のリストの様々な関数を使いこなすための練習問題集のPart 2 (中級編) である。15問を通して、F#のリスト操作の中級レベルの技術をマスターすることを目指す。
問題16: リストの要素を左から畳み込む
入力:
[1; 2; 3; 4; 5]- 初期値:
0 - 畳み込み関数:
fun acc x -> acc + x
出力:
15
ヒント
List.fold関数を使用するList.foldiは、畳み込み関数にインデックスも渡したい場合に使用する
解答例と解説
let list = [1; 2; 3; 4; 5]let initialValue = 0let foldFunction = (fun acc x -> acc + x)let result = List.fold foldFunction initialValue list
printfn "%d" result解説:
List.fold 関数は、リストの各要素に対して指定された関数を左から順に適用し、累積値を計算する。List.fold foldFunction initialValue list は、まず initialValue と list の最初の要素を foldFunction に適用し、次にその結果とリストの2番目の要素を foldFunction に適用し、これをリストの最後まで繰り返す。
この例では、foldFunction は fun acc x -> acc + x であり、累積値 acc と現在の要素 x を加算する。初期値は 0 であるため、計算の流れは以下のようになる:
acc = 0,x = 1:acc + x = 1acc = 1,x = 2:acc + x = 3acc = 3,x = 3:acc + x = 6acc = 6,x = 4:acc + x = 10acc = 10,x = 5:acc + x = 15
最終的に result は 15 になる。List.foldi を使うと、畳み込み関数に要素のインデックスも渡される。
問題17: リストの要素を右から畳み込む
入力:
["a"; "b"; "c"]- 初期値:
"" - 畳み込み関数:
fun x acc -> x + acc
出力:
"cba"
ヒント
List.foldBack関数を使用するList.foldBackiは、畳み込み関数にインデックスも渡したい場合に使用する
解答例と解説
let list = ["a"; "b"; "c"]let initialValue = ""let foldBackFunction = (fun x acc -> x + acc)let result = List.foldBack foldBackFunction list initialValue
printfn "%s" result解説:
List.foldBack 関数は、List.fold と似ているが、リストの末尾から先頭に向かって関数を適用する。List.foldBack foldBackFunction list initialValue は、まず list の最後の要素と initialValue を foldBackFunction に適用し、次にその結果とリストの後ろから2番目の要素を foldBackFunction に適用し、これをリストの先頭まで繰り返す。
この例では、foldBackFunction は fun x acc -> x + acc であり、現在の要素 x を累積値 acc の前に結合する。初期値は "" であるため、計算の流れは以下のようになる:
x = "c",acc = "":x + acc = "c"x = "b",acc = "c":x + acc = "bc"x = "a",acc = "bc":x + acc = "abc"
最終的に result は "cba" になる。List.foldBacki を使うと、畳み込み関数に要素のインデックスも渡される。
問題18: リストの最初の要素を初期値として畳み込む
入力:
[5; 2; 9; 1; 5; 6]- 畳み込み関数:
fun x y -> if x > y then x else y
出力:
9
ヒント
List.reduce関数を使用する
解答例と解説
let list = [5; 2; 9; 1; 5; 6]let reduceFunction = (fun x y -> if x > y then x else y)let result = List.reduce reduceFunction list
printfn "%d" result解説:
List.reduce 関数は List.fold と似ているが、初期値を必要とせず、リストの最初の要素を初期値として使用する。List.reduce reduceFunction list は、まず list の最初の要素と2番目の要素を reduceFunction に適用し、次にその結果と3番目の要素を reduceFunction に適用し、これをリストの最後まで繰り返す。
この例では、reduceFunction は fun x y -> if x > y then x else y であり、2つの要素のうち大きい方を返す。計算の流れは以下のようになる:
x = 5,y = 2:x > yなので5x = 5,y = 9:x > yではないので9x = 9,y = 1:x > yなので9x = 9,y = 5:x > yなので9x = 9,y = 6:x > yなので9
最終的に result は 9 になる。これは、List.max を使って最大値を求めるのと同じ結果になる。注意: 空リストに List.reduce を適用すると例外が発生する。
問題19: リストの最後の要素を初期値として畳み込む
入力:
[1; 2; 3]- 畳み込み関数:
fun x y -> x - y
出力:
0((3 - 2) - 1 = 0となる)
ヒント
List.reduceBack関数を使用する
解答例と解説
let list = [1; 2; 3]let reduceBackFunction = (fun x y -> x - y)let result = List.reduceBack reduceBackFunction list
printfn "%d" result解説:
List.reduceBack 関数は List.reduce と似ているが、リストの末尾から先頭に向かって関数を適用する。List.reduceBack reduceBackFunction list は、まず list の最後の要素と後ろから2番目の要素を reduceBackFunction に適用し、次にその結果と後ろから3番目の要素を reduceBackFunction に適用し、これをリストの先頭まで繰り返す。
この例では、reduceBackFunction は fun x y -> x - y であり、x から y を引く関数である。計算の流れは以下のようになる:
x = 3,y = 2:x - y = 1x = 1,y = 1:x - y = 0
最終的に result は 0 になる。List.reduceBack は右結合的に計算されることに注意。つまり、(3 - 2) - 1 のように計算される。注意: 空リストに List.reduceBack を適用すると例外が発生する。
問題20: リストの各要素に関数を適用し、結果のリストを結合する
入力:
[1; 2; 3]- 適用する関数:
fun x -> [x; x * x]
出力:
[1; 1; 2; 4; 3; 9]
ヒント
List.collect関数を使用する
解答例と解説
let list = [1; 2; 3]let func = (fun x -> [x; x * x])let result = List.collect func list
printfn "%A" result解説:
List.collect 関数は、リストの各要素に関数を適用し、その結果 (リスト) をすべて結合して1つのリストにする。この例では、func は各要素 x に対して [x; x * x] というリストを返す。List.collect はそれらのリストを結合して [1; 1; 2; 4; 3; 9] を返す。
x = 1:func xは[1; 1]x = 2:func xは[2; 4]x = 3:func xは[3; 9]
これらのリストが結合されて、[1; 1; 2; 4; 3; 9] となる。ちなみに、TypeScriptにおける flatMap に相当する。
問題21: リストの要素をデフォルトの比較関数で昇順にソートする
入力:
[5; 2; 9; 1; 5; 6]
出力:
[1; 2; 5; 5; 6; 9]
ヒント
List.sort関数を使用するList.sortDescendingは降順にソートする
解答例と解説
let list = [5; 2; 9; 1; 5; 6]let sortedList = List.sort list
printfn "%A" sortedList解説:
List.sort 関数は、リストの要素をデフォルトの比較関数で昇順にソートした新しいリストを返す。この例では、sortedList は [1; 2; 5; 5; 6; 9] となる。数値は小さい順、文字列は辞書順にソートされる。List.sortDescending を使うと降順にソートできる。
問題22: リストの要素を指定されたキーに基づいて昇順にソートする
入力:
["apple"; "banana"; "orange"]- ソートキー: 文字列の長さ
出力:
["apple"; "orange"; "banana"]
ヒント
List.sortBy関数を使用するList.sortByDescendingはキーに基づいて降順にソートする
解答例と解説
let list = ["apple"; "banana"; "orange"]let sortKey = (fun s -> s.Length)let sortedList = List.sortBy sortKey list
printfn "%A" sortedList解説:
List.sortBy 関数は、リストの各要素からキーを抽出し、そのキーに基づいて昇順にソートした新しいリストを返す。この例では、sortKey として fun s -> s.Length という関数を使い、各文字列 s の長さをキーとしている。sortedList は ["apple"; "orange"; "banana"] となる。List.sortByDescending を使うと、キーに基づいて降順にソートできる。
問題23: リストの要素をカスタム比較関数を使ってソートする
入力:
[1; -2; 3; -4; 5]- 比較関数:
fun x y -> abs x - abs y(絶対値で比較)
出力:
[1; -2; 3; -4; 5](絶対値が小さい順に並ぶ)
ヒント
List.sortWith関数を使用する
解答例と解説
let list = [1; -2; 3; -4; 5]let compareFunction = (fun x y -> abs x - abs y)let sortedList = List.sortWith compareFunction list
printfn "%A" sortedList解説:
List.sortWith 関数は、指定された比較関数を使ってリストの要素をソートした新しいリストを返す。比較関数は2つの要素 x と y を受け取り、x が y より小さい場合は負の値、x が y と等しい場合は0、x が y より大きい場合は正の値を返す必要がある。
この例では、compareFunction は fun x y -> abs x - abs y であり、要素の絶対値で比較を行う。sortedList は [1; -2; 3; -4; 5] となり、絶対値が小さい順に並ぶ。
問題24: 2つのリストを組み合わせてタプルのリストを作成する
入力:
[1; 2; 3]["a"; "b"; "c"]
出力:
[(1, "a"); (2, "b"); (3, "c")]
ヒント
List.zip関数を使用するList.zip3は3つのリストを組み合わせる
解答例と解説
let list1 = [1; 2; 3]let list2 = ["a"; "b"; "c"]let zippedList = List.zip list1 list2
printfn "%A" zippedList解説:
List.zip 関数は、2つのリストの対応する要素をペアにしたタプルのリストを返す。この例では、zippedList は [(1, "a"); (2, "b"); (3, "c")] となる。List.zip3 を使うと、3つのリストを組み合わせることができる。
注意: 2つのリストの長さが異なる場合、短い方のリストの長さに合わせてタプルのリストが作成され、長い方のリストの余った要素は無視される。
問題25: タプルのリストを2つのリストに分解する
入力:
[(1, "a"); (2, "b"); (3, "c")]
出力:
[1; 2; 3]["a"; "b"; "c"]
ヒント
List.unzip関数を使用するList.unzip3は3つの要素を持つタプルのリストを分解する
解答例と解説
let zippedList = [(1, "a"); (2, "b"); (3, "c")]let (list1, list2) = List.unzip zippedList
printfn "%A" list1printfn "%A" list2解説:
List.unzip 関数は、List.zip の逆の操作で、タプルのリストを2つのリストに分解する。この例では、list1 は [1; 2; 3]、list2 は ["a"; "b"; "c"] となる。List.unzip3 を使うと、3つの要素を持つタプルのリストを3つのリストに分解できる。
問題26: リストを条件に基づいて2つのリストに分割する
入力:
[1; 2; 3; 4; 5; 6]- 条件: 偶数かどうか
出力:
- 偶数のリスト:
[2; 4; 6] - 奇数のリスト:
[1; 3; 5]
ヒント
List.partition関数を使用する
解答例と解説
let list = [1; 2; 3; 4; 5; 6]let isEven = (fun x -> x % 2 = 0)let (evenList, oddList) = List.partition isEven list
printfn "%A" evenListprintfn "%A" oddList解説:
List.partition 関数は、リストの各要素に条件を適用し、条件を満たす要素のリストと満たさない要素のリストのタプルを返す。この例では、isEven として fun x -> x % 2 = 0 という関数を使い、偶数と奇数に分割している。evenList は [2; 4; 6]、oddList は [1; 3; 5] となる。
問題27: 共通のキーに基づいてリストの要素をグループ化する
入力:
[("apple", 1); ("banana", 2); ("orange", 1); ("grape", 2); ("kiwi", 1)]- キー: タプルの2番目の要素 (果物の数)
出力:
[(1, ["apple"; "orange"; "kiwi"]); (2, ["banana"; "grape"])]
ヒント
List.groupBy関数を使用する
解答例と解説
let list = [("apple", 1); ("banana", 2); ("orange", 1); ("grape", 2); ("kiwi", 1)]let keyFunction = (fun (fruit, count) -> count)let groupedList = List.groupBy keyFunction list
printfn "%A" groupedList解説:
List.groupBy 関数は、リストの各要素からキーを抽出し、そのキーに基づいて要素をグループ化する。この例では、keyFunction として fun (fruit, count) -> count という関数を使い、各タプルの2番目の要素 (果物の数) をキーとしている。List.groupBy は、キーと、そのキーを持つ要素のリストのペアのリストを返す。結果として、groupedList は [(1, ["apple"; "orange"; "kiwi"]); (2, ["banana"; "grape"])] となる。
問題28: リストから重複する要素を取り除く
入力:
[1; 2; 2; 3; 4; 4; 4; 5]
出力:
[1; 2; 3; 4; 5]
ヒント
List.distinct関数を使用する
解答例と解説
let list = [1; 2; 2; 3; 4; 4; 4; 5]let distinctList = List.distinct list
printfn "%A" distinctList解説:
List.distinct 関数は、リストから重複する要素を取り除き、一意な要素のみを含む新しいリストを返す。要素の順序は保持される。この例では、distinctList は [1; 2; 3; 4; 5] となる。
問題29: 指定されたキーに基づいて、リストから重複する要素を取り除く
入力:
["apple"; "banana"; "orange"; "grape"; "kiwi"]- キー: 文字列の最初の文字
出力:
["apple"; "banana"; "orange"; "grape"]
ヒント
List.distinctBy関数を使用する
解答例と解説
let list = ["apple"; "banana"; "orange"; "grape"; "kiwi"]let keyFunction = (fun s -> s.[0])let distinctList = List.distinctBy keyFunction list
printfn "%A" distinctList解説:
List.distinctBy 関数は、指定されたキーに基づいてリストから重複する要素を取り除き、一意な要素のみを含む新しいリストを返す。キーが同じであれば、最初の要素が保持され、残りは破棄される。この例では、keyFunction として fun s -> s.[0] という関数を使い、各文字列の最初の文字をキーとしている。結果として、distinctList は ["apple"; "banana"; "orange"; "grape"] となる。
問題30: リストの各要素に関数を適用し、最初に Some を返した結果を取得する
入力:
[1; 2; 3; 4; 5]- 適用する関数:
fun x -> if x > 3 then Some(x * 2) else None
出力:
Some 8
ヒント
List.pick関数を使用するList.tryPickは、結果をOption型で返す
解答例と解説
let list = [1; 2; 3; 4; 5]let pickFunction = (fun x -> if x > 3 then Some(x * 2) else None)let result = List.pick pickFunction list
printfn "%A" result解説:
List.pick 関数は、リストの各要素に関数を適用し、その関数が最初に Some を返したときの、その Some の中身を返す。この例では、pickFunction は x > 3 を満たす最初の要素 4 に対して Some(4 * 2)、つまり Some 8 を返す。したがって、result は Some 8 となる。条件を満たす要素がない場合、List.pick は例外を発生させる。List.tryPick を使うと、例外の代わりに None が返されるため、より安全である。
問題31: リストの各要素に関数を適用し、Some の値だけを新しいリストに含める
入力:
[1; 2; 3; 4; 5]- 適用する関数:
fun x -> if x % 2 = 0 then Some(x * 2) else None
出力:
[4; 8]
ヒント
List.choose関数を使用する
解答例と解説
let list = [1; 2; 3; 4; 5]let chooseFunction = (fun x -> if x % 2 = 0 then Some(x * 2) else None)let result = List.choose chooseFunction list
printfn "%A" result解説:
List.choose 関数は、リストの各要素に関数を適用し、その結果が Some の値である場合、その値を含む新しいリストを作成する。None の場合は無視される。この例では、chooseFunction は偶数 x に対して Some(x * 2) を、奇数に対して None を返す。List.choose は Some の中身だけを新しいリストに追加するため、result は [4; 8] となる。
問題32: リストに特定の要素が含まれているかを判定する
入力:
[1; 2; 3; 4; 5]- 検索する要素:
3
出力:
true
ヒント
List.contains関数を使用する
解答例と解説
let list = [1; 2; 3; 4; 5]let elementToFind = 3let containsElement = List.contains elementToFind list
printfn "%b" containsElement解説:
List.contains 関数は、リストに指定された要素が含まれているかどうかを判定する。この例では、list に elementToFind (3) が含まれているため、containsElement は true になる。
問題33: 指定された要素を指定された回数繰り返したリストを生成する
入力:
- 繰り返す要素:
"a" - 繰り返す回数:
3
出力:
["a"; "a"; "a"]
ヒント
List.replicate関数を使用する
解答例と解説
let element = "a"let count = 3let replicatedList = List.replicate count element
printfn "%A" replicatedList解説:
List.replicate 関数は、指定された要素を指定された回数繰り返したリストを生成する。この例では、replicatedList は ["a"; "a"; "a"] となる。
問題34: リストの各要素を左から畳み込み、途中経過も保持する
入力:
[1; 2; 3; 4; 5]- 初期値:
0 - 畳み込み関数:
fun acc x -> acc + x
出力:
[0; 1; 3; 6; 10; 15]
ヒント
List.scan関数を使用するList.scaniは、畳み込み関数にインデックスも渡したい場合に使用する
解答例と解説
let list = [1; 2; 3; 4; 5]let initialValue = 0let scanFunction = (fun acc x -> acc + x)let result = List.scan scanFunction initialValue list
printfn "%A" result解説:
List.scan 関数は List.fold と似ているが、途中経過を含むリストを返す。List.scan scanFunction initialValue list は、まず initialValue をリストの先頭に追加し、その後 List.fold と同様に畳み込み演算を行う。
この例では、scanFunction は fun acc x -> acc + x であり、累積値 acc と現在の要素 x を加算する。初期値は 0 であるため、計算の流れは以下のようになる:
initialValue(0) をリストの先頭に追加:[0]acc = 0,x = 1:acc + x = 1->[0; 1]acc = 1,x = 2:acc + x = 3->[0; 1; 3]acc = 3,x = 3:acc + x = 6->[0; 1; 3; 6]acc = 6,x = 4:acc + x = 10->[0; 1; 3; 6; 10]acc = 10,x = 5:acc + x = 15->[0; 1; 3; 6; 10; 15]
最終的に result は [0; 1; 3; 6; 10; 15] になる。List.scani を使うと、畳み込み関数に要素のインデックスも渡される。
問題35: リストの各要素を右から畳み込み、途中経過も保持する
入力:
["a"; "b"; "c"]- 初期値:
"" - 畳み込み関数:
fun x acc -> x + acc
出力:
["cba"; "cb"; "c"; ""]
ヒント
List.scanBack関数を使用するList.scanBackiは、畳み込み関数にインデックスも渡したい場合に使用する
解答例と解説
let list = ["a"; "b"; "c"]let initialValue = ""let scanBackFunction = (fun x acc -> x + acc)let result = List.scanBack scanBackFunction list initialValue
printfn "%A" result解説:
List.scanBack 関数は List.foldBack と似ているが、途中経過を含むリストを返す。List.scanBack scanBackFunction list initialValue は、まず initialValue をリストの末尾に追加し、その後 List.foldBack と同様に畳み込み演算を行う。
この例では、scanBackFunction は fun x acc -> x + acc であり、現在の要素 x を累積値 acc の前に結合する。初期値は "" であるため、計算の流れは以下のようになる:
initialValue(空文字列) をリストの末尾に追加:[""]x = "c",acc = "":x + acc = "c"->["c"; ""]x = "b",acc = "c":x + acc = "bc"->["bc"; "c"; ""]x = "a",acc = "bc":x + acc = "abc"->["abc"; "bc"; "c"; ""]
最終的に result は ["cba"; "cb"; "c"; ""] になる。List.scanBacki を使うと、畳み込み関数に要素のインデックスも渡される。