数组 (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
模块支持对一维数组进行操作。 模块 Array2D
、Array3D
和 Array4D
分别包含支持对二维、三维和四维数组进行操作的函数。 你可以使用 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.exists
和 Array.exists2
分别测试一个或两个数组中的元素。 如果存在满足条件的元素(对于 Array.exists2
,则为元素对),这些函数会采用测试函数并返回 true
。
以下代码演示了 Array.exists
和 Array.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.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
的用法。 此示例定义了几个本地帮助程序函数来简化代码,而不是使用 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.max
或 Array.min
获取最大或最小元素。 同样,Array.maxBy
和 Array.minBy
允许先执行函数,也许是为了转换为支持比较的类型。
Array.sum
将数组的元素相加,Array.sumBy
对每个元素调用函数并将结果相加。
若要对数组中的每个元素执行函数而不存储返回值,请使用 Array.iter
。 对于涉及两个长度相等的数组的函数,请使用 Array.iter2
。 如果还需要保留函数结果的数组,请使用一次对两个数组进行操作的 Array.map
或 Array.map2
。
变体 Array.iteri
和 Array.iteri2
允许元素的索引参与计算;Array.mapi
和 Array.mapi2
也是如此。
函数 Array.fold
、Array.foldBack
、Array.reduce
、Array.reduceBack
、Array.scan
和 Array.scanBack
执行涉及数组所有元素的算法。 同样,变体 Array.fold2
和 Array.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.toList
和 Array.toSeq
从数组类型转换为这些其他集合类型。
数组排序
使用 Array.sort
通过泛型比较函数对数组进行排序。 使用 Array.sortBy
指定生成值(称为键)的函数,以便通过对键使用泛型比较函数进行排序。 如果要提供自定义比较函数,请使用 Array.sortWith
。 Array.sort
、Array.sortBy
和 Array.sortWith
都将排序后的数组作为新数组返回。 变体 Array.sortInPlace
、Array.sortInPlaceBy
和 Array.sortInPlaceWith
修改现有数组,而不是返回新数组。
数组和元组
函数 Array.zip
和 Array.unzip
将元组对数组转换为数组元组,反之亦然。 Array.zip3
和 Array.unzip3
类似,只不过它们适用于包含三个元素的元组或包含三个数组的元组。
对数组执行并行计算
模块 Array.Parallel
包含对数组执行并行计算的函数。 此模块在面向 .NET Framework 4 之前版本的应用程序中不可用。