数组 (F#)
数组是由相同类型的连续数据元素构成的、具有固定大小且从零开始的可变集合。
创建数组
创建数组的方式有多种。 可以通过以下方式创建小型数组:在 [| 和 |] 之间列出若干个连续的值,各个值之间用分号分隔,如下面的示例所示。
let array1 = [| 1; 2; 3 |]
也可以将每个元素置于单独的行上,在此情况下,加不加分号分隔符均可。
let array1 =
[|
1
2
3
|]
数组元素的类型将根据使用的文本推理得出,必须保持一致。 下面的代码会导致错误,因为 1.0 是一个浮点数,而 2 和 3 都是整数。
// Causes an error.
// let array2 = [| 1.0; 2; 3 |]
也可以使用序列表达式来创建数组。 下面的示例创建一个从 1 到 10 的整数的平方值的数组。
let array3 = [| for i in 1 .. 10 -> i * i |]
若要创建一个所有元素都初始化为零的数组,请使用 Array.zeroCreate。
let arrayOfTenZeroes : int array = Array.zeroCreate 10
访问元素
可以使用点运算符 (.) 和括号([ 和 ])来访问数组元素。
array1.[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 类型 Array。 因此,F# 数组支持 Array 中可用的所有功能。
库模块 Microsoft.FSharp.Collections.Array 支持对一维数组执行的操作。 模块 Array2D、Array3D 和 Array4D 分别包含支持对二维数组、三维数组和四维数组执行的操作的函数。 可以使用 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 会在两个数组中同时出现,因为对 StringBuilder 类型执行 Insert 操作会影响两个数组中引用的基础 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。
let multiplicationTable max = seq { for i in 1 .. max -> [| for j in 1 .. max -> (i, j, i*j) |] }
printfn "%A" (Array.concat (multiplicationTable 3))
上述代码的输出结果如下。
[|(1, 1, 1); (1, 2, 2); (1, 3, 3); (2, 1, 2); (2, 2, 4); (2, 3, 6); (3, 1, 3);
(3, 2, 6); (3, 3, 9)|]
Array.filter 可采用一个布尔条件函数作为参数,并生成一个新数组,其中仅包含输入数组中满足条件的元素。 下面的代码演示了 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>[,,],更多维数的数组的类型编写方式可以依此类推。
仅对一维数组可用的函数的子集也对多维数组可用。 有关更多信息,请参见Collections.Array 模块 (F#)、Collections.Array2D 模块 (F#)、Collections.Array3D 模块 (F#)和Collections.Array4D 模块 (F#)。
针对数组的布尔函数
函数 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 的第一个元素;如果找不到符合条件的元素,则将引发 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
如果除了查找元素之外还需要对其进行转换,请使用 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.
对数组执行计算
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 模块中具有相同名称的函数相对应。 有关用法示例,请参见列表 (F#)。
修改数组
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 包含用于对数组执行并行计算的函数。 在以版本 4 之前的 .NET Framework 版本为目标的应用程序中,此模块不可用。