陣列 (F#)

陣列是固定大小、從零開始,全部具有相同類型的可變連續資料元素集合。

建立陣列

您可以透過數種方式建立陣列。 您可以列出 [||] 之間的連續值,並以分號分隔,以建立小型陣列,如下列範例所示。

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 是 Tuple 且 3 是整數。

// Causes an error too. The 3 (integer) cannot be converted to tuple implicitly.
// let array4 = [| 1, 2; 3 |]

您也可以使用序列運算式來建立陣列。 下列範例會建立整數 1 到 10 的平方所形成的陣列。

let array3 = [| for i in 1 .. 10 -> i * i |]

若要建立陣列,並將其中的所有元素初始化為零,請使用 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 模組可支援一維陣列上的作業。 Array2DArray3DArray4D 模組則包含函式,分別可以支援二維、三維和四維陣列上的作業。 您可以使用 System.Array 來建立秩大於四的陣列。

簡單函式

Array.get 會取得元素。 Array.length 會提供陣列的長度。 Array.set 會將元素設定為指定值。 下列程式碼範例會示範如何使用這些函式。

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 字串出現在這兩個陣列,因為 System.Text.StringBuilder 類型上的 Insert 作業會影響這兩個陣列中所參考的基礎 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 會結合兩個現有陣列來建立新的陣列。

下列程式碼示範 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 會採用一連串的陣列,並將其合併成單一陣列。 下列程式碼會示範 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 運算子即可從一連串的陣列元素序列建立陣列。 序列可以是陣列或清單常值。 例如,下列程式碼會建立二維陣列。

let my2DArray = array2D [ [ 1; 0]; [0; 1] ]

您也可以使用 Array2D.init 函式來初始化二維陣列,三維和四維陣列也有類似的函式。 這些函式會採用用來建立元素的函式。 若要建立二維陣列並於其中包含設定為初始值的元素,而不是指定函式,請使用 Array2D.create 函式,另外,這個函式也可用於最多四維的陣列。 下列程式碼範例會先示範如何建立由多個包含所需元素的陣列所形成的陣列,然後使用 Array2D.init 來產生所需的二維陣列。

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

二維陣列的類型可寫為 <type>[,] (例如,int[,], double[,]),三維陣列的類型可寫為 <type>[,,],較高維度陣列的寫法依此類推。

適用於一維陣列的函式中,只有一部分也適用於多維陣列。

陣列配量和多維陣列

在二維陣列 (矩陣) 中,您可以藉由指定範圍並使用萬用字元 (*) 來指定整個資料列或資料行,以擷取子矩陣。

// 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 方法的類型,使用此配量語法。 例如,下列程式碼會建立 Matrix 類型來包裝 F# 2D 陣列、實作 Item 屬性以提供陣列索引支援,以及實作三個版本的 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 函式可分別測試一個陣列或兩個陣列中的元素。 這些函式會採用測試函式,並在有元素 (若為 Array.exists2,則是元素組) 符合條件時傳回 true

下列程式碼會示範如何使用 Array.existsArray.exists2。 這些範例會藉由只套用其中一個引數 (在這些案例中為函式引數) 來建立新的函式。

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 函式也會測試陣列,以判斷每個元素是否都符合布林值條件。 Array.forall2 變體函式會使用布林值函式 (涉及兩個等長陣列的元素) 來進行相同工作。 下列程式碼會示範如何使用這些函式。

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.KeyNotFoundExceptionArray.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 的用法。 在此情況下,會定義數個本機協助程式函式來簡化程式碼,而不是定義 Lambda 運算式。

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.maxArray.min 來取得最大元素或最小元素。 同樣地,Array.maxByArray.minBy 能夠先執行函式,目的或許是為了轉換成支援進行比較的類型。

Array.sum 會將陣列的元素相加,Array.sumBy 則會在每個元素上呼叫函式並將結果相加起來。

若要在陣列中的每個元素上執行函式,但不要儲存傳回值,請使用 Array.iter。 對於涉及兩個等長陣列的函式,請使用 Array.iter2。 如果您還需要保留函式結果所形成的陣列,請使用 Array.mapArray.map2,以便一次在兩個陣列上進行運算。

Array.iteriArray.iteri2 變體函式可讓計算時涉及元素的索引;Array.mapiArray.mapi2 也是如此。

Array.foldArray.foldBackArray.reduceArray.reduceBackArray.scanArray.scanBack 函式會執行涉及陣列所有元素的演算法。 同樣地,Array.fold2Array.foldBack2 變體函式會在兩個陣列上執行計算。

這些用於執行計算的函式會對應至 List 模組中的同名函式。 如需使用範例,請參閱清單

修改陣列

Array.set 會將元素設定為指定值。 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 將某個陣列的子區段複製到另一個陣列。

轉換為其他類型以及從其他類型轉換過來

Array.ofList 會從清單建立陣列。 Array.ofSeq 會從序列建立陣列。 Array.toListArray.toSeq 會從陣列類型轉換為這些其他集合類型。

排序陣列

使用 Array.sort 可使用泛型比較函式來排序陣列。 使用 Array.sortBy 可指定函式來產生稱為「索引鍵」的值,以在索引鍵上使用泛型比較函式來進行排序。 如果您想要提供自訂的比較函式,請使用 Array.sortWithArray.sortArray.sortByArray.sortWith 都會將排序後的陣列傳回為新的陣列。 Array.sortInPlaceArray.sortInPlaceByArray.sortInPlaceWith 變體函式會修改現有陣列,而不是傳回新的陣列。

陣列和元組

Array.zipArray.unzip 函式會將成對元組的陣列轉換為陣列的元組,反之亦然。 Array.zip3Array.unzip3 類似,不同之處在於其會使用有三個元素的元組或有三個陣列的元組。

陣列上的平行計算

Array.Parallel 模組包含用於對陣列執行平行計算的函式。 此模組不適用於以第 4 版之前的 .NET Framework 版本為目標的應用程式。

另請參閱