切片
本文介绍如何从现有的 F# 类型中获取切片,以及如何自定义切片。
在 F# 中,切片是任何数据类型的子集。 切片与索引器相似,但它不是从基础数据结构生成单个值,而是生成多个值。 切片使用 ..
运算符语法来选择数据类型中指定索引的范围。 有关详细信息,请参阅循环表达式参考文章。
F# 目前提供对切片字符串、列表、数组和多维(2D、3D、4D)数组的内部支持。 切片最常用于 F# 数组和列表。 通过在类型定义中或在范围内的类型扩展中使用 GetSlice
方法,可以向自定义数据类型添加切片。
对 F# 列表和数组进行切片
最常见的进行切片的数据类型是 F# 列表和数组。 下面的示例演示如何对列表进行切片:
// Generate a list of 100 integers
let fullList = [ 1 .. 100 ]
// Create a slice from indices 1-5 (inclusive)
let smallSlice = fullList[1..5]
printfn $"Small slice: {smallSlice}"
// Create a slice from the beginning to index 5 (inclusive)
let unboundedBeginning = fullList[..5]
printfn $"Unbounded beginning slice: {unboundedBeginning}"
// Create a slice from an index to the end of the list
let unboundedEnd = fullList[94..]
printfn $"Unbounded end slice: {unboundedEnd}"
对数组进行切片与对列表进行切片的过程类似:
// Generate an array of 100 integers
let fullArray = [| 1 .. 100 |]
// Create a slice from indices 1-5 (inclusive)
let smallSlice = fullArray[1..5]
printfn $"Small slice: {smallSlice}"
// Create a slice from the beginning to index 5 (inclusive)
let unboundedBeginning = fullArray[..5]
printfn $"Unbounded beginning slice: {unboundedBeginning}"
// Create a slice from an index to the end of the list
let unboundedEnd = fullArray[94..]
printfn $"Unbounded end slice: {unboundedEnd}"
在 F# 6 之前,切片使用带附加 .
的语法 expr.[start..finish]
。 如果你愿意,仍可使用此语法。 有关详细信息,请参阅 RFC FS-1110。
对多维数组进行切片
F# 支持 F# 核心库中的多维数组。 与一维数组一样,多维数组的切片也很有用。 但随着其他维度的引入,需要使用略微不同的语法才能获取特定行和列的切片。
下面的示例演示如何对 2D 数组进行切片:
// Generate a 3x3 2D matrix
let A = array2D [[1;2;3];[4;5;6];[7;8;9]]
printfn $"Full matrix:\n {A}"
// Take the first row
let row0 = A[0,*]
printfn $"{row0}"
// Take the first column
let col0 = A[*,0]
printfn $"{col0}"
// Take all rows but only two columns
let subA = A[*,0..1]
printfn $"{subA}"
// Take two rows and all columns
let subA' = A[0..1,*]
printfn $"{subA}"
// Slice a 2x2 matrix out of the full 3x3 matrix
let twoByTwo = A[0..1,0..1]
printfn $"{twoByTwo}"
为其他数据结构定义切片
F# 核心库为一组有限的类型定义了切片。 如果要为更多数据类型定义切片,可在类型定义本身或类型扩展中执行此操作。
例如,下面介绍了如何为 ArraySegment<T> 类定义切片,以便于操作数据:
open System
type ArraySegment<'TItem> with
member segment.GetSlice(start, finish) =
let start = defaultArg start 0
let finish = defaultArg finish segment.Count
ArraySegment(segment.Array, segment.Offset + start, finish - start)
let arr = ArraySegment [| 1 .. 10 |]
let slice = arr[2..5] //[ 3; 4; 5]
使用 Span<T> 和 ReadOnlySpan<T> 类型的另一个示例:
open System
type ReadOnlySpan<'T> with
member sp.GetSlice(startIdx, endIdx) =
let s = defaultArg startIdx 0
let e = defaultArg endIdx sp.Length
sp.Slice(s, e - s)
type Span<'T> with
member sp.GetSlice(startIdx, endIdx) =
let s = defaultArg startIdx 0
let e = defaultArg endIdx sp.Length
sp.Slice(s, e - s)
let printSpan (sp: Span<int>) =
let arr = sp.ToArray()
printfn $"{arr}"
let sp = [| 1; 2; 3; 4; 5 |].AsSpan()
printSpan sp[0..] // [|1; 2; 3; 4; 5|]
printSpan sp[..5] // [|1; 2; 3; 4; 5|]
printSpan sp[0..3] // [|1; 2; 3|]
printSpan sp[1..3] // |2; 3|]
内置的 F# 切片包含末端
F# 中的所有内部切片都含包含末端;也就是说,切片中包含上限。 对于具有起始索引 x
和结束索引 y
的给定切片,生成的切片将包含第 y 个值。
// Define a new list
let xs = [1 .. 10]
printfn $"{xs[2..5]}" // Includes the 5th index
内置的 F# 空切片
如果语法可能生成不存在的切片,则 F# 列表、数组、序列、字符串、多维(2D、3D、4D)数组将生成一个空切片。
请考虑以下示例:
let l = [ 1..10 ]
let a = [| 1..10 |]
let s = "hello!"
let emptyList = l[-2..(-1)]
let emptyArray = a[-2..(-1)]
let emptyString = s[-2..(-1)]
重要
C# 开发人员可能希望它们引发异常,而不是生成空切片。 该设计决策是基于空集合以 F# 编写这一事实。 空 F# 列表可能由另一个 F# 列表构成,空字符串可能会添加到现有字符串,等等。 基于以参数形式传入的值获取切片很常见,此外通过生成空集合来允许超出范围 > 符合 F# 代码的组合特性。
3D 和 4D 数组的固定索引切片
对于 F# 3D 和 4D 数组,可以“固定”特定索引,并对已固定索引的其他维度进行分片。
为了说明这一点,请参考以下 3D 数组:
z = 0
x\y | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 2 | 3 |
z = 1
x\y | 0 | 1 |
---|---|---|
0 | 4 | 5 |
1 | 6 | 7 |
如果要从数组中提取切片 [| 4; 5 |]
,请使用固定索引切片。
let dim = 2
let m = Array3D.zeroCreate<int> dim dim dim
let mutable count = 0
for z in 0..dim-1 do
for y in 0..dim-1 do
for x in 0..dim-1 do
m[x,y,z] <- count
count <- count + 1
// Now let's get the [4;5] slice!
m[*, 0, 1]
最后一行固定 3D 数组的 y
和 z
索引,并获取对应于矩阵的其余 x
值。