数组 (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 是元组,而 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 方法的类型。 例如,以下代码将创建包装 F# 二维数组的 Matrix 类型,实现 Item 属性以提供对数组索引的支持,并实现 GetSlice 的三个版本。 如果可以将此代码用作 Matrix 类型的模板,则可以使用本部分所述的所有切片操作。

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 包含对数组执行并行计算的函数。 此模块在面向 .NET Framework 4 之前版本的应用程序中不可用。

另请参阅