配列 (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 次元配列に対する操作がサポートされます。 Array2DArray3DArray4D の各モジュールには、それぞれ、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.existsArray.exists2 により、それぞれ 1 つまたは 2 つの配列内の要素がテストされます。 これらの関数は、テスト用の関数を受け取り、要素 (または true の場合は要素のペア) のうち、条件を満たす要素がある場合は Array.exists2 を返します。

次のコードは、Array.existsArray.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.findIndexArray.find に似ていますが、要素自体ではなく、要素のインデックスを返す点が異なります。

次のコードでは、Array.findArray.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.tryFindArray.find に似ていますが、結果がオプション型であり、要素が見つからない場合は None を返す点が異なります。 一致する要素が配列内にあるかどうかわからない場合は、Array.tryFind ではなく、Array.find を使用してください。 同様に、Array.tryFindIndexArray.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.maxByArray.minBy によって、比較がサポートされている型に変換する目的などのために、最初に関数を実行できます。

Array.sum では、配列の各要素が加算されます。Array.sumBy では、各要素に対して関数が呼び出され、その結果が加算されます。

戻り値を保存しないで、配列の各要素に対して関数を実行するには、Array.iter を使用します。 同じ長さの 2 つの配列を対象とする関数の場合は、Array.iter2 を使用します。 また、関数の結果の配列を保持する必要がある場合は、Array.map または Array.map2 を使用します。後者は一度に 2 つの配列を処理します。

類似の Array.iteriArray.iteri2 を使用すると、要素のインデックスを計算に含めることができます。Array.mapiArray.mapi2 についても同様です。

Array.foldArray.foldBackArray.reduceArray.reduceBackArray.scanArray.scanBack の各関数により、配列のすべての要素を対象とするアルゴリズムが実行されます。 同様に、類似の Array.fold2Array.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.toListArray.toSeq によって、配列型から別のコレクション型に変換されます。

配列の並べ替え

汎用の比較関数を使用して配列を並べ替えるには、Array.sort を使用します。 "Array.sortBy" と呼ばれる値を生成する関数を指定し、そのキーに基づいて汎用の比較関数を使用して並べ替えを行う場合は、Array.sortBy を使用します。 独自の比較関数を使用する必要がある場合は、Array.sortWith を使用します。 Array.sortArray.sortBy、および Array.sortWith は、いずれも、並べ替えた配列を新しい配列として返します。 類似関数 Array.sortInPlaceArray.sortInPlaceBy、および Array.sortInPlaceWith では、新しいものを返す代わりに既存の配列が変更されます。

配列とタプル

Array.zip および Array.unzip 関数では、タプルのペアから成る配列が、配列から成るタプルに変換されます。また、その逆の変換も行われます。 Array.zip3Array.unzip3 も同様ですが、3 つの要素から成るタプルまたは 3 つの配列から成るタプルを対象とする点が異なります。

配列に対する並列計算

Array.Parallel モジュールには、配列に対して並列計算を実行するための関数が含まれます。 このモジュールは、Version 4 より前の .NET Framework を対象とするアプリケーションでは使用できません。

関連項目