配列 (F#)
配列は、0 から始まる一連のデータ要素の、固定サイズの変更可能なコレクションで、その型はすべて同じです。
配列の作成
配列は複数の方法で作成できます。 小さなサイズの配列は、[|
と |]
の間に、連続する値をセミコロンで区切って列記して作成できます。次に例を示します。
let array1 = [| 1; 2; 3 |]
各要素を別々の行に配置することもでき、その場合は、セミコロンの区切り記号を省略できます。
let array1 =
[|
1
2
3
|]
配列の要素の型は、使用されるリテラルから推論され、すべて同じである必要があります。
// This is an array of 3 integers.
let array1 = [| 1; 2; 3 |]
// This is an array of a tuple of 3 integers.
let array2 = [| 1, 2, 3 |]
次のコードでは、3.0 が浮動小数点数で、1 と 2 は整数であるため、エラーになります。
// Causes an error. The 3.0 (float) cannot be converted to integer implicitly.
// let array3 = [| 1; 2; 3.0 |]
次のコードも、1,2
がタプルで、3
は整数であるため、エラーになります。
// Causes an error too. The 3 (integer) cannot be converted to tuple implicitly.
// let array4 = [| 1, 2; 3 |]
シーケンス式を使用して配列を作成することもできます。 1 から 10 までの整数の 2 乗の配列を作成する例を次に示します。
let array3 = [| for i in 1 .. 10 -> i * i |]
すべての要素が 0 に初期化される配列を作成するには、Array.zeroCreate
を使用します。
let arrayOfTenZeroes : int array = Array.zeroCreate 10
要素のアクセス
配列要素には、角かっこ ([
と ]
) を使ってアクセスできます。 元のドット構文 (.[index]
) は引き続きサポートされていますが、F# 6.0 以降では推奨されなくなりました。
array1[0]
配列のインデックスは 0 から始まります。
また、スライス表記を使用して配列の要素にアクセスすることもできます。この方法では、配列のサブ範囲を指定できます。 スライス表記の例を次に示します。
// Accesses elements from 0 to 2.
array1[0..2]
// Accesses elements from the beginning of the array to 2.
array1[..2]
// Accesses elements from 2 to the end of the array.
array1[2..]
スライス表記を使用するときは、配列の新しいコピーが作成されます。
配列の型とモジュール
F# の配列の型はすべて、.NET Framework 型の System.Array です。 したがって、F# の配列では、System.Array で使用できるすべての機能がサポートされます。
Array
モジュールでは、1 次元配列に対する操作がサポートされます。 Array2D
、Array3D
、Array4D
の各モジュールには、それぞれ、2 次元、3 次元、4 次元の配列の操作をサポートする関数があります。 4 より大きいランクの配列は、System.Array を使用して作成できます。
単純な関数
Array.get
によって要素が取得されます。 Array.length
は配列の長さを示します。 Array.set
によって、1 つの要素が指定した値に設定されます。 これらの関数の使い方を次のコード例に示します。
let array1 = Array.create 10 ""
for i in 0 .. array1.Length - 1 do
Array.set array1 i (i.ToString())
for i in 0 .. array1.Length - 1 do
printf "%s " (Array.get array1 i)
出力は次のとおりです。
0 1 2 3 4 5 6 7 8 9
配列を作成する関数
既存の配列を必要とせずに配列を作成できる関数がいくつかあります。 Array.empty
によって、要素をまったく含まない新しい配列が作成されます。 Array.create
によって、指定したサイズの配列が作成され、すべての要素が指定した値に設定されます。 Array.init
によって、次元が指定された配列と、要素を生成する関数が作成されます。 Array.zeroCreate
によって、すべての要素が配列の型のゼロ値に初期化された配列が作成されます。 これらの関数の例を次のコードに示します。
let myEmptyArray = Array.empty
printfn "Length of empty array: %d" myEmptyArray.Length
printfn "Array of floats set to 5.0: %A" (Array.create 10 5.0)
printfn "Array of squares: %A" (Array.init 10 (fun index -> index * index))
let (myZeroArray : float array) = Array.zeroCreate 10
出力は次のとおりです。
Length of empty array: 0
Area of floats set to 5.0: [|5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0|]
Array of squares: [|0; 1; 4; 9; 16; 25; 36; 49; 64; 81|]
Array.copy
によって、既存の配列からコピーされた要素を含む新しい配列が作成されます。 コピーは簡易コピーです。つまり、要素の型が参照型である場合は、参照だけがコピーされ、基になっているオブジェクトはコピーされません。 これを次のコード例に示します。
open System.Text
let firstArray : StringBuilder array = Array.init 3 (fun index -> new StringBuilder(""))
let secondArray = Array.copy firstArray
// Reset an element of the first array to a new value.
firstArray[0] <- new StringBuilder("Test1")
// Change an element of the first array.
firstArray[1].Insert(0, "Test2") |> ignore
printfn "%A" firstArray
printfn "%A" secondArray
このコードによる出力は、次のようになります。
[|Test1; Test2; |]
[|; Test2; |]
Test1
という文字列は、最初の配列にのみ表示されます。これは、firstArray
の参照が、新しい要素を作成する操作によって上書きされるものの、空の文字列への元の参照は影響を受けず、secondArray
にまだ存在しているためです。 Test2
という文字列は、両方の配列で表示されます。これは、Insert
型に対する System.Text.StringBuilder 操作は基になっている System.Text.StringBuilder オブジェクトに影響を与え、このオブジェクトは両方の配列で参照されているためです。
Array.sub
によって、配列のサブ範囲から新しい配列が生成されます。 サブ範囲は、開始インデックスと長さで指定します。 Array.sub
を使用したコードの例を次に示します。
let a1 = [| 0 .. 99 |]
let a2 = Array.sub a1 5 10
printfn "%A" a2
出力では、サブ配列が要素 5 から開始し、10 個の要素を含むことが示されています。
[|5; 6; 7; 8; 9; 10; 11; 12; 13; 14|]
Array.append
によって、既存の 2 つの配列を結合することで、新しい配列が作成されます。
次のコードは、Array.append を示しています。
printfn "%A" (Array.append [| 1; 2; 3|] [| 4; 5; 6|])
このコードによる出力は、次のようになります。
[|1; 2; 3; 4; 5; 6|]
Array.choose
によって、新しい配列に含める配列の要素が選択されます。 次のコードで Array.choose
の例を示します。 配列の要素の型は、オプションの型で返される値の型と一致している必要はありません。 この例では、要素の型は int
ですが、オプションは多項式関数 elem*elem - 1
の結果であり、浮動小数点数です。
printfn "%A" (Array.choose (fun elem -> if elem % 2 = 0 then
Some(float (elem*elem - 1))
else
None) [| 1 .. 10 |])
このコードによる出力は、次のようになります。
[|3.0; 15.0; 35.0; 63.0; 99.0|]
Array.collect
によって、指定された関数が既存の配列の各配列要素に対して実行され、その関数によって生成された要素が収集されて、それらが新しい配列にまとめられます。 次のコードで Array.collect
の例を示します。
printfn "%A" (Array.collect (fun elem -> [| 0 .. elem |]) [| 1; 5; 10|])
このコードによる出力は、次のようになります。
[|0; 1; 0; 1; 2; 3; 4; 5; 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]
Array.concat
では、受け取った一連の配列が 1 つの配列にまとめられます。 次のコードで Array.concat
の例を示します。
Array.concat [ [|0..3|] ; [|4|] ]
//output [|0; 1; 2; 3; 4|]
Array.concat [| [|0..3|] ; [|4|] |]
//output [|0; 1; 2; 3; 4|]
Array.filter
では、ブール条件の関数を受け取り、入力配列の要素のうち、条件が true のもののみを含む新しい配列が生成されます。 次のコードで Array.filter
の例を示します。
printfn "%A" (Array.filter (fun elem -> elem % 2 = 0) [| 1 .. 10|])
このコードによる出力は、次のようになります。
[|2; 4; 6; 8; 10|]
Array.rev
によって、既存の配列を逆順にした新しい配列が生成されます。 次のコードで Array.rev
の例を示します。
let stringReverse (s: string) =
System.String(Array.rev (s.ToCharArray()))
printfn "%A" (stringReverse("!dlrow olleH"))
このコードによる出力は、次のようになります。
"Hello world!"
次の例に示すように、パイプライン演算子 (|>
) を使用することで、配列モジュールの配列変換関数を簡単に結合できます。
[| 1 .. 10 |]
|> Array.filter (fun elem -> elem % 2 = 0)
|> Array.choose (fun elem -> if (elem <> 8) then Some(elem*elem) else None)
|> Array.rev
|> printfn "%A"
出力は次のようになります。
[|100; 36; 16; 4|]
多次元配列
多次元配列を作成できますが、多次元配列リテラルを記述するための構文はありません。 配列要素の一連のシーケンスから配列を作成するには、array2D
演算子を使用します。 シーケンスには、配列リテラルまたはリスト リテラルを使用できます。 たとえば、次のコードでは 2 次元の配列が作成されます。
let my2DArray = array2D [ [ 1; 0]; [0; 1] ]
また、Array2D.init
関数を使用すると、2 次元の配列を初期化できます。3 および 4 次元の配列にも同様の関数があります。 これらの関数は、要素の作成に使用する関数を受け取ります。 関数を指定するのではなく、初期値に設定された要素を含む 2 次元配列を作成するには、Array2D.create
関数を使用します。これは、最大 4 次元までの配列にも使用できます。 次のコード例では、まず、目的の要素を含む複数の配列から成る 1 つの配列を作成し、次に、Array2D.init
を使用して目的の 2 次元配列を生成する方法を示します。
let arrayOfArrays = [| [| 1.0; 0.0 |]; [|0.0; 1.0 |] |]
let twoDimensionalArray = Array2D.init 2 2 (fun i j -> arrayOfArrays[i][j])
配列インデックスとスライス構文は、4 ランクまでの配列に使用できます。 複数の次元のインデックスを指定する場合は、次のコード例に示すように、コンマを使用してインデックスを区切ります。
twoDimensionalArray[0, 1] <- 1.0
2 次元配列の型は <type>[,]
として書き出され (int[,]
、double[,]
など)、3 次元配列の型は <type>[,,]
として書き出されます。このように、次元が高くなるにつれ、書き出される型が変わります。
1 次元配列で使用できる関数のサブセットのうち、多次元配列でも使用できるのは一部だけです。
配列スライスと多次元配列
2 次元配列 (行列) で、範囲を指定し、ワイルドカード (*
) を使用して行または列全体を指定することにより、サブ行列を抽出できます。
// Get rows 1 to N from an NxM matrix (returns a matrix):
matrix[1.., *]
// Get rows 1 to 3 from a matrix (returns a matrix):
matrix[1..3, *]
// Get columns 1 to 3 from a matrix (returns a matrix):
matrix[*, 1..3]
// Get a 3x3 submatrix:
matrix[1..3, 1..3]
多次元配列を同じまたはより低い次元のサブ配列に分解することができます。 たとえば、単一の行または列を指定して、行列からベクターを取得できます。
// Get row 3 from a matrix as a vector:
matrix[3, *]
// Get column 3 from a matrix as a vector:
matrix[*, 3]
このスライス構文は、要素アクセス演算子およびオーバーロードされた GetSlice
メソッドを実装する型に使用できます。 たとえば、次のコードは、F# 2D 配列をラップし、配列のインデックスのサポートを提供する項目プロパティを実装し、3 つのバージョンの GetSlice
を実装するマトリックス型を作成します。 マトリックス型のテンプレートとしてこのコードの使用が可能であれば、このセクションで説明するすべてのスライスの操作を使用できます。
type Matrix<'T>(N: int, M: int) =
let internalArray = Array2D.zeroCreate<'T> N M
member this.Item
with get(a: int, b: int) = internalArray[a, b]
and set(a: int, b: int) (value:'T) = internalArray[a, b] <- value
member this.GetSlice(rowStart: int option, rowFinish : int option, colStart: int option, colFinish : int option) =
let rowStart =
match rowStart with
| Some(v) -> v
| None -> 0
let rowFinish =
match rowFinish with
| Some(v) -> v
| None -> internalArray.GetLength(0) - 1
let colStart =
match colStart with
| Some(v) -> v
| None -> 0
let colFinish =
match colFinish with
| Some(v) -> v
| None -> internalArray.GetLength(1) - 1
internalArray[rowStart..rowFinish, colStart..colFinish]
member this.GetSlice(row: int, colStart: int option, colFinish: int option) =
let colStart =
match colStart with
| Some(v) -> v
| None -> 0
let colFinish =
match colFinish with
| Some(v) -> v
| None -> internalArray.GetLength(1) - 1
internalArray[row, colStart..colFinish]
member this.GetSlice(rowStart: int option, rowFinish: int option, col: int) =
let rowStart =
match rowStart with
| Some(v) -> v
| None -> 0
let rowFinish =
match rowFinish with
| Some(v) -> v
| None -> internalArray.GetLength(0) - 1
internalArray[rowStart..rowFinish, col]
module test =
let generateTestMatrix x y =
let matrix = new Matrix<float>(3, 3)
for i in 0..2 do
for j in 0..2 do
matrix[i, j] <- float(i) * x - float(j) * y
matrix
let test1 = generateTestMatrix 2.3 1.1
let submatrix = test1[0..1, 0..1]
printfn $"{submatrix}"
let firstRow = test1[0,*]
let secondRow = test1[1,*]
let firstCol = test1[*,0]
printfn $"{firstCol}"
配列に対するブール関数
関数 Array.exists
と Array.exists2
により、それぞれ 1 つまたは 2 つの配列内の要素がテストされます。 これらの関数は、テスト用の関数を受け取り、要素 (または true
の場合は要素のペア) のうち、条件を満たす要素がある場合は Array.exists2
を返します。
次のコードは、Array.exists
と Array.exists2
の使用方法を示しています。 これらの例では、ただ 1 つの引数 (この場合は関数引数) を適用することで、新しい関数を作成しています。
let allNegative = Array.exists (fun elem -> abs (elem) = elem) >> not
printfn "%A" (allNegative [| -1; -2; -3 |])
printfn "%A" (allNegative [| -10; -1; 5 |])
printfn "%A" (allNegative [| 0 |])
let haveEqualElement = Array.exists2 (fun elem1 elem2 -> elem1 = elem2)
printfn "%A" (haveEqualElement [| 1; 2; 3 |] [| 3; 2; 1|])
このコードによる出力は、次のようになります。
true
false
false
true
同様に、Array.forall
関数により、1 つの配列がテストされて、各要素がブール条件を満たすかどうかが判別されます。 類似の Array.forall2
では、同じ長さの 2 つの配列の要素を対象とするブール関数を使用して、同じ機能が実行されます。 これらの関数の使い方を次のコード例に示します。
let allPositive = Array.forall (fun elem -> elem > 0)
printfn "%A" (allPositive [| 0; 1; 2; 3 |])
printfn "%A" (allPositive [| 1; 2; 3 |])
let allEqual = Array.forall2 (fun elem1 elem2 -> elem1 = elem2)
printfn "%A" (allEqual [| 1; 2 |] [| 1; 2 |])
printfn "%A" (allEqual [| 1; 2 |] [| 2; 1 |])
これらの例の出力は次のようになります。
false
true
true
false
配列の検索
Array.find
では、ブール関数を受け取り、その関数から true
が返る最初の要素が返されます。条件を満たす要素が見つからない場合は、System.Collections.Generic.KeyNotFoundException が発生します。 Array.findIndex
は Array.find
に似ていますが、要素自体ではなく、要素のインデックスを返す点が異なります。
次のコードでは、Array.find
と Array.findIndex
を使用して、完全平方かつ完全立方である値を探しています。
let arrayA = [| 2 .. 100 |]
let delta = 1.0e-10
let isPerfectSquare (x:int) =
let y = sqrt (float x)
abs(y - round y) < delta
let isPerfectCube (x:int) =
let y = System.Math.Pow(float x, 1.0/3.0)
abs(y - round y) < delta
let element = Array.find (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
let index = Array.findIndex (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
printfn "The first element that is both a square and a cube is %d and its index is %d." element index
出力は次のとおりです。
The first element that is both a square and a cube is 64 and its index is 62.
Array.tryFind
は Array.find
に似ていますが、結果がオプション型であり、要素が見つからない場合は None
を返す点が異なります。 一致する要素が配列内にあるかどうかわからない場合は、Array.tryFind
ではなく、Array.find
を使用してください。 同様に、Array.tryFindIndex
は Array.findIndex
に似ていますが、オプション型が戻り値である点が異なります。 要素が見つからない場合、オプションは None
です。
Array.tryFind
を使用したコードの例を次に示します。 このコードでは、前に示したコードを利用しています。
let delta = 1.0e-10
let isPerfectSquare (x:int) =
let y = sqrt (float x)
abs(y - round y) < delta
let isPerfectCube (x:int) =
let y = System.Math.Pow(float x, 1.0/3.0)
abs(y - round y) < delta
let lookForCubeAndSquare array1 =
let result = Array.tryFind (fun elem -> isPerfectSquare elem && isPerfectCube elem) array1
match result with
| Some x -> printfn "Found an element: %d" x
| None -> printfn "Failed to find a matching element."
lookForCubeAndSquare [| 1 .. 10 |]
lookForCubeAndSquare [| 100 .. 1000 |]
lookForCubeAndSquare [| 2 .. 50 |]
出力は次のとおりです。
Found an element: 1
Found an element: 729
Failed to find a matching element.
要素を見つけるだけでなく、変換する必要がある場合は、Array.tryPick
を使用します。 この関数の結果は、変換した要素をオプションの値として返した最初の要素になります。該当する要素が見つからない場合は、None
を返します。
Array.tryPick
の使用方法を次のコードに示します。 この例では、ラムダ式の代わりに、複数のローカル ヘルパー関数を定義することでコードを簡単にしています。
let findPerfectSquareAndCube array1 =
let delta = 1.0e-10
let isPerfectSquare (x:int) =
let y = sqrt (float x)
abs(y - round y) < delta
let isPerfectCube (x:int) =
let y = System.Math.Pow(float x, 1.0/3.0)
abs(y - round y) < delta
// intFunction : (float -> float) -> int -> int
// Allows the use of a floating point function with integers.
let intFunction function1 number = int (round (function1 (float number)))
let cubeRoot x = System.Math.Pow(x, 1.0/3.0)
// testElement: int -> (int * int * int) option
// Test an element to see whether it is a perfect square and a perfect
// cube, and, if so, return the element, square root, and cube root
// as an option value. Otherwise, return None.
let testElement elem =
if isPerfectSquare elem && isPerfectCube elem then
Some(elem, intFunction sqrt elem, intFunction cubeRoot elem)
else None
match Array.tryPick testElement array1 with
| Some (n, sqrt, cuberoot) -> printfn "Found an element %d with square root %d and cube root %d." n sqrt cuberoot
| None -> printfn "Did not find an element that is both a perfect square and a perfect cube."
findPerfectSquareAndCube [| 1 .. 10 |]
findPerfectSquareAndCube [| 2 .. 100 |]
findPerfectSquareAndCube [| 100 .. 1000 |]
findPerfectSquareAndCube [| 1000 .. 10000 |]
findPerfectSquareAndCube [| 2 .. 50 |]
出力は次のとおりです。
Found an element 1 with square root 1 and cube root 1.
Found an element 64 with square root 8 and cube root 4.
Found an element 729 with square root 27 and cube root 9.
Found an element 4096 with square root 64 and cube root 16.
Did not find an element that is both a perfect square and a perfect cube.
配列に対する計算の実行
Array.average
関数によって、配列の各要素の平均が返されます。 この関数を使用できるのは、整数による正確な除算がサポートされている要素型だけです。そのため、浮動小数点型では使用できますが、整数型では使用できません。 Array.averageBy
関数によって、各要素に対して関数を呼び出した結果の平均が返されます。 整数型の配列の場合は、Array.averageBy
を使用し、この関数で各要素を浮動小数点型に変換して計算することもできます。
最大または最小の要素を取得するには、Array.max
または Array.min
を使用します (要素型でサポートされている場合)。 同様に、Array.maxBy
と Array.minBy
によって、比較がサポートされている型に変換する目的などのために、最初に関数を実行できます。
Array.sum
では、配列の各要素が加算されます。Array.sumBy
では、各要素に対して関数が呼び出され、その結果が加算されます。
戻り値を保存しないで、配列の各要素に対して関数を実行するには、Array.iter
を使用します。 同じ長さの 2 つの配列を対象とする関数の場合は、Array.iter2
を使用します。 また、関数の結果の配列を保持する必要がある場合は、Array.map
または Array.map2
を使用します。後者は一度に 2 つの配列を処理します。
類似の Array.iteri
と Array.iteri2
を使用すると、要素のインデックスを計算に含めることができます。Array.mapi
と Array.mapi2
についても同様です。
Array.fold
、Array.foldBack
、Array.reduce
、Array.reduceBack
、Array.scan
、Array.scanBack
の各関数により、配列のすべての要素を対象とするアルゴリズムが実行されます。 同様に、類似の Array.fold2
と Array.foldBack2
では、2 つの配列に対して計算が実行されます。
計算を実行するこれらの関数は、List モジュール内の同じ名前の関数に対応しています。 使用例については、「リスト」を参照してください。
配列の変更
Array.set
によって、1 つの要素が指定した値に設定されます。 Array.fill
によって、配列内の特定範囲の要素が、指定した値に設定されます。 Array.fill
のコード例を次に示します。
let arrayFill1 = [| 1 .. 25 |]
Array.fill arrayFill1 2 20 0
printfn "%A" arrayFill1
出力は次のとおりです。
[|1; 2; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 23; 24; 25|]
Array.blit
を使用すると、1 つの配列の一部を別の配列にコピーできます。
他の型との相互変換
Array.ofList
によって、リストから配列が作成されます。 Array.ofSeq
によって、シーケンスから配列が作成されます。 Array.toList
と Array.toSeq
によって、配列型から別のコレクション型に変換されます。
配列の並べ替え
汎用の比較関数を使用して配列を並べ替えるには、Array.sort
を使用します。 "Array.sortBy
" と呼ばれる値を生成する関数を指定し、そのキーに基づいて汎用の比較関数を使用して並べ替えを行う場合は、Array.sortBy
を使用します。 独自の比較関数を使用する必要がある場合は、Array.sortWith
を使用します。 Array.sort
、Array.sortBy
、および Array.sortWith
は、いずれも、並べ替えた配列を新しい配列として返します。 類似関数 Array.sortInPlace
、Array.sortInPlaceBy
、および Array.sortInPlaceWith
では、新しいものを返す代わりに既存の配列が変更されます。
配列とタプル
Array.zip
および Array.unzip
関数では、タプルのペアから成る配列が、配列から成るタプルに変換されます。また、その逆の変換も行われます。 Array.zip3
と Array.unzip3
も同様ですが、3 つの要素から成るタプルまたは 3 つの配列から成るタプルを対象とする点が異なります。
配列に対する並列計算
Array.Parallel
モジュールには、配列に対して並列計算を実行するための関数が含まれます。 このモジュールは、Version 4 より前の .NET Framework を対象とするアプリケーションでは使用できません。
関連項目
.NET