序列是所有一种类型的元素的逻辑序列。 当你拥有大量有序的数据收集,但不一定期望使用所有元素时,序列特别有用。 单个序列元素仅根据需要进行计算,因此,在不使用所有元素的情况下,序列可以提供比列表更好的性能。 序列由 seq<'T>
类型表示,该类型是别名 IEnumerable<T>。 因此,任何实现接口的 IEnumerable<T> .NET 类型都可以用作序列。
Seq 模块支持涉及序列的作。
序列表达式
序列表达式是计算结果为序列的表达式。 序列表达式可以采用多种形式。 最简单的窗体指定范围。 例如, seq { 1 .. 5 }
创建包含五个元素(包括终结点 1 和 5)的序列。 还可以在两个双周期之间指定增量(或递减)。 例如,以下代码创建 10 的倍数序列。
// Sequence that has an increment.
seq { 0..10..100 }
序列表达式由生成序列值的 F# 表达式组成。 还可以以编程方式生成值:
seq { for i in 1..10 -> i * i }
上一个示例使用 ->
运算符,该运算符允许指定其值将成为序列的一部分的表达式。 仅当后面的代码的每个部分都返回一个值时,才能使用 ->
。
或者,可以使用以下可选yield
选项指定do
关键字:
seq {
for i in 1..10 do
yield i * i
}
// The 'yield' is implicit and doesn't need to be specified in most cases.
seq {
for i in 1..10 do
i * i
}
以下代码将生成坐标对列表以及索引到表示网格的数组中。 请注意,第一个 for
表达式需要指定一个 do
。
let (height, width) = (10, 10)
seq {
for row in 0 .. width - 1 do
for col in 0 .. height - 1 -> (row, col, row * width + col)
}
if
序列中使用的表达式是筛选器。 例如,若要仅生成质数序列,假设你有一个类型int -> bool
函数isprime
,请按如下所示构造序列。
seq {
for n in 1..100 do
if isprime n then
n
}
如前所述,此处是必需的,->
,将收到一个错误,指出并非所有分支都返回一个值。
yield!
关键字
有时,你可能希望将一系列元素包含在另一个序列中。 若要在另一个序列中包含序列,需要使用 yield!
关键字:
// Repeats '1 2 3 4 5' ten times
seq {
for _ in 1..10 do
yield! seq { 1; 2; 3; 4; 5}
}
另一种思考 yield!
方法是,它平展内部序列,然后在包含序列中包含它。
在表达式中使用时 yield!
,所有其他单个值都必须使用 yield
关键字:
// Combine repeated values with their values
seq {
for x in 1..10 do
yield x
yield! seq { for i in 1..x -> i}
}
上一个示例将生成x
除每个x
值以外的所有值1
x
的值。
例子
第一个示例使用包含迭代、筛选器和生成数组的序列表达式。 此代码将 1 到 100 之间的质数序列打印到控制台。
// Recursive isprime function.
let isprime n =
let rec check i =
i > n / 2 || (n % i <> 0 && check (i + 1))
check 2
let aSequence =
seq {
for n in 1..100 do
if isprime n then
n
}
for x in aSequence do
printfn "%d" x
以下示例创建一个乘法表,其中包含三个元素的元组,每个元素由两个因素和产品组成:
let multiplicationTable =
seq {
for i in 1..9 do
for j in 1..9 -> (i, j, i * j)
}
以下示例演示如何将 yield!
各个序列合并为单个最终序列。 在这种情况下,二进制树中每个子树的序列在递归函数中串联,以生成最终序列。
// Yield the values of a binary tree in a sequence.
type Tree<'a> =
| Tree of 'a * Tree<'a> * Tree<'a>
| Leaf of 'a
// inorder : Tree<'a> -> seq<'a>
let rec inorder tree =
seq {
match tree with
| Tree(x, left, right) ->
yield! inorder left
yield x
yield! inorder right
| Leaf x -> yield x
}
let mytree = Tree(6, Tree(2, Leaf(1), Leaf(3)), Leaf(9))
let seq1 = inorder mytree
printfn "%A" seq1
使用序列
序列支持与 列表相同的许多函数。 序列还支持使用键生成函数进行分组和计数等作。 序列还支持用于提取子序列的更多样化的函数。
许多数据类型(如列表、数组、集和映射)都是隐式序列,因为它们是可枚举集合。 除实现 System.Collections.Generic.IEnumerable<'T>
的任何 .NET 数据类型外,采用序列作为参数的函数也适用于任何常见的 F# 数据类型。 这与采用列表作为参数的函数形成对比,该函数只能采用列表。 该类型seq<'T>
是类型缩写。IEnumerable<'T>
这意味着实现泛型 System.Collections.Generic.IEnumerable<'T>
的任何类型(包括 F# 中的数组、列表、集和映射)以及大多数 .NET 集合类型都与 seq
该类型兼容,并且可在预期序列的位置使用。
模块函数
FSharp.Collections 命名空间中的 Seq 模块包含用于处理序列的函数。 这些函数也适用于列表、数组、映射和集,因为这些类型都是可枚举的,因此可以被视为序列。
创建序列
可以使用序列表达式(如前所述)或使用某些函数来创建序列。
可以使用 Seq.empty 创建空序列,也可以使用 Seq.singleton 仅创建一个指定元素的序列。
let seqEmpty = Seq.empty
let seqOne = Seq.singleton 10
可以使用 Seq.init 创建一个序列,这些元素是使用你提供的函数创建的。 还可以为序列提供大小。 此函数与 List.init 类似,只是在循环访问序列之前不会创建元素。 以下代码演示了 Seq.init
如何使用 。
let seqFirst5MultiplesOf10 = Seq.init 5 (fun n -> n * 10)
Seq.iter (fun elem -> printf "%d " elem) seqFirst5MultiplesOf10
输出为
0 10 20 30 40
通过使用 Seq.ofArray 和 Seq.ofList'T<> 函数,可以从数组和列表创建序列。 但是,还可以使用强制转换运算符将数组和列表转换为序列。 以下代码中显示了这两种技术。
// Convert an array to a sequence by using a cast.
let seqFromArray1 = [| 1 .. 10 |] :> seq<int>
// Convert an array to a sequence by using Seq.ofArray.
let seqFromArray2 = [| 1 .. 10 |] |> Seq.ofArray
通过使用 Seq.cast,可以从弱类型集合(例如在 <System.Object
,并使用非泛型 System.Collections.Generic.IEnumerable`1
类型枚举。 以下代码演示了如何使用 Seq.cast
转换为 System.Collections.ArrayList
序列。
open System
let arr = ResizeArray<int>(10)
for i in 1 .. 10 do
arr.Add(10)
let seqCast = Seq.cast arr
可以使用 Seq.initInfinite 函数定义无限序列。 对于此类序列,提供一个函数,该函数从元素的索引生成每个元素。 由于延迟计算,无限序列是可能的;通过调用指定的函数根据需要创建元素。 下面的代码示例生成一个无限的浮点数序列,在本例中,连续整数的正方形的交替序列。
let seqInfinite =
Seq.initInfinite (fun index ->
let n = float (index + 1)
1.0 / (n * n * (if ((index + 1) % 2 = 0) then 1.0 else -1.0)))
printfn "%A" seqInfinite
Seq.unfold 从计算函数生成一个序列,该函数采用状态并对其进行转换以生成序列中的每个后续元素。 状态只是一个值,用于计算每个元素,并且可以在计算每个元素时更改。 第二个参数 Seq.unfold
是用于启动序列的初始值。
Seq.unfold
使用状态的选项类型,通过返回 None
值可以终止序列。 下面的代码演示了两个序列示例, seq1
以及 fib
由作 unfold
生成的序列。 第一 seq1
个,只是一个简单的序列,数字高达 20。 第二个, fib
用于 unfold
计算 Fibonacci 序列。 由于 Fibonacci 序列中的每个元素都是前两个 Fibonacci 数字的总和,因此状态值是一个元组,它由序列中的前两个数字组成。 初始值为 (0,1)
序列中的前两个数字。
let seq1 =
0 // Initial state
|> Seq.unfold (fun state ->
if (state > 20) then
None
else
Some(state, state + 1))
printfn "The sequence seq1 contains numbers from 0 to 20."
for x in seq1 do
printf "%d " x
let fib =
(0, 1)
|> Seq.unfold (fun state ->
let cur, next = state
if cur < 0 then // overflow
None
else
let next' = cur + next
let state' = next, next'
Some (cur, state') )
printfn "\nThe sequence fib contains Fibonacci numbers."
for x in fib do printf "%d " x
输出如下所示:
The sequence seq1 contains numbers from 0 to 20.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
The sequence fib contains Fibonacci numbers.
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
下面的代码是一个示例,它使用此处所述的许多序列模块函数来生成和计算无限序列的值。 运行代码可能需要几分钟时间。
// generateInfiniteSequence generates sequences of floating point
// numbers. The sequences generated are computed from the fDenominator
// function, which has the type (int -> float) and computes the
// denominator of each term in the sequence from the index of that
// term. The isAlternating parameter is true if the sequence has
// alternating signs.
let generateInfiniteSequence fDenominator isAlternating =
if (isAlternating) then
Seq.initInfinite (fun index ->
1.0 /(fDenominator index) * (if (index % 2 = 0) then -1.0 else 1.0))
else
Seq.initInfinite (fun index -> 1.0 /(fDenominator index))
// The harmonic alternating series is like the harmonic series
// except that it has alternating signs.
let harmonicAlternatingSeries = generateInfiniteSequence (fun index -> float index) true
// This is the series of reciprocals of the odd numbers.
let oddNumberSeries = generateInfiniteSequence (fun index -> float (2 * index - 1)) true
// This is the series of recipocals of the squares.
let squaresSeries = generateInfiniteSequence (fun index -> float (index * index)) false
// This function sums a sequence, up to the specified number of terms.
let sumSeq length sequence =
(0, 0.0)
|>
Seq.unfold (fun state ->
let subtotal = snd state + Seq.item (fst state + 1) sequence
if (fst state >= length) then
None
else
Some(subtotal, (fst state + 1, subtotal)))
// This function sums an infinite sequence up to a given value
// for the difference (epsilon) between subsequent terms,
// up to a maximum number of terms, whichever is reached first.
let infiniteSum infiniteSeq epsilon maxIteration =
infiniteSeq
|> sumSeq maxIteration
|> Seq.pairwise
|> Seq.takeWhile (fun elem -> abs (snd elem - fst elem) > epsilon)
|> List.ofSeq
|> List.rev
|> List.head
|> snd
// Compute the sums for three sequences that converge, and compare
// the sums to the expected theoretical values.
let result1 = infiniteSum harmonicAlternatingSeries 0.00001 100000
printfn "Result: %f ln2: %f" result1 (log 2.0)
let pi = Math.PI
let result2 = infiniteSum oddNumberSeries 0.00001 10000
printfn "Result: %f pi/4: %f" result2 (pi/4.0)
// Because this is not an alternating series, a much smaller epsilon
// value and more terms are needed to obtain an accurate result.
let result3 = infiniteSum squaresSeries 0.0000001 1000000
printfn "Result: %f pi*pi/6: %f" result3 (pi*pi/6.0)
搜索和查找元素
序列支持列表可用的功能:Seq.exists、Seq.exists2、Seq.find、Seq.findIndex、Seq.pick、Seq.tryFind 和 Seq.tryFindIndex。 可用于序列的这些函数的版本仅计算要搜索的元素的序列。 有关示例,请参阅 列表。
获取 Subsequences
Seq.filter 和 Seq.choose 类似于可用于列表的相应函数,只是在计算序列元素之前不会进行筛选和选择。
Seq.truncate 从另一个序列创建序列,但将序列限制为指定数量的元素。
Seq.take 创建一个新序列,该序列仅包含序列开头的指定数量的元素。 如果序列中的元素数少于指定要采用的元素, Seq.take
则引发一个 System.InvalidOperationException
。 如果元素数小于指定的数字,则Seq.truncate
Seq.truncate
这两者之间的差异Seq.take
不会生成错误。
以下代码显示了和 // 之间的 Seq.truncate
行为和 Seq.take
差异。
let mySeq = seq { for i in 1 .. 10 -> i*i }
let truncatedSeq = Seq.truncate 5 mySeq
let takenSeq = Seq.take 5 mySeq
let truncatedSeq2 = Seq.truncate 20 mySeq
let takenSeq2 = Seq.take 20 mySeq
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
// Up to this point, the sequences are not evaluated.
// The following code causes the sequences to be evaluated.
truncatedSeq |> printSeq
truncatedSeq2 |> printSeq
takenSeq |> printSeq
// The following line produces a run-time error (in printSeq):
takenSeq2 |> printSeq
在发生错误之前,输出如下所示。
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
1 4 9 16 25
1 4 9 16 25 36 49 64 81 100
通过使用 Seq.takeWhile,可以指定谓词函数(布尔函数),并从由谓词返回的原始序列的元素组成的另一个序列 true
创建序列,但在谓词返回 false
的第一个元素之前停止。
Seq.skip 返回一个序列,该序列跳过另一个序列的第一个元素的指定数目,并返回其余元素。
Seq.skipWhile 返回一个序列,只要谓词返回 true
,然后返回剩余的元素,从谓词返回 false
的第一个元素开始。
下面的代码示例演示了和 /和之间的Seq.takeWhile
Seq.skip
行为和Seq.skipWhile
差异。
// takeWhile
let mySeqLessThan10 = Seq.takeWhile (fun elem -> elem < 10) mySeq
mySeqLessThan10 |> printSeq
// skip
let mySeqSkipFirst5 = Seq.skip 5 mySeq
mySeqSkipFirst5 |> printSeq
// skipWhile
let mySeqSkipWhileLessThan10 = Seq.skipWhile (fun elem -> elem < 10) mySeq
mySeqSkipWhileLessThan10 |> printSeq
输出如下所示。
1 4 9
36 49 64 81 100
16 25 36 49 64 81 100
转换序列
Seq.pairwise 创建一个新序列,在该序列中,输入序列的连续元素分组为元组。
let printSeq seq1 = Seq.iter (printf "%A ") seq1; printfn ""
let seqPairwise = Seq.pairwise (seq { for i in 1 .. 10 -> i*i })
printSeq seqPairwise
printfn ""
let seqDelta = Seq.map (fun elem -> snd elem - fst elem) seqPairwise
printSeq seqDelta
Seq.windowed 与 Seq.pairwise
生成元组序列不同,而是生成包含序列中相邻元素( 窗口)副本的数组序列。 指定每个数组中所需的相邻元素数。
下面的代码示例演示了如何使用 Seq.windowed
。 在这种情况下,窗口中的元素数为 3。 该示例使用 printSeq
上一个代码示例中定义的示例。
let seqNumbers = [ 1.0; 1.5; 2.0; 1.5; 1.0; 1.5 ] :> seq<float>
let seqWindows = Seq.windowed 3 seqNumbers
let seqMovingAverage = Seq.map Array.average seqWindows
printfn "Initial sequence: "
printSeq seqNumbers
printfn "\nWindows of length 3: "
printSeq seqWindows
printfn "\nMoving average: "
printSeq seqMovingAverage
输出如下所示。
初始序列:
1.0 1.5 2.0 1.5 1.0 1.5
Windows of length 3:
[|1.0; 1.5; 2.0|] [|1.5; 2.0; 1.5|] [|2.0; 1.5; 1.0|] [|1.5; 1.0; 1.5|]
Moving average:
1.5 1.666666667 1.5 1.333333333
具有多个序列的作
Seq.zip 和 Seq.zip3 采用两个或三个序列并生成元组序列。 这些函数类似于可用于 列表的相应函数。 没有相应的功能可将一个序列分成两个或多个序列。 如果需要序列的此功能,请将序列转换为列表并使用 List.unzip。
排序、比较和分组
列表支持的排序函数也适用于序列。 这包括 Seq.sort 和 Seq.sortBy。 这些函数循环访问整个序列。
使用 Seq.compareWith 函数比较两个序列。 该函数依次比较连续元素,并在遇到第一个不相等对时停止。 任何其他元素都不会影响比较。
以下代码演示了 Seq.compareWith
如何使用 。
let sequence1 = seq { 1 .. 10 }
let sequence2 = seq { 10 .. -1 .. 1 }
// Compare two sequences element by element.
let compareSequences =
Seq.compareWith (fun elem1 elem2 ->
if elem1 > elem2 then 1
elif elem1 < elem2 then -1
else 0)
let compareResult1 = compareSequences sequence1 sequence2
match compareResult1 with
| 1 -> printfn "Sequence1 is greater than sequence2."
| -1 -> printfn "Sequence1 is less than sequence2."
| 0 -> printfn "Sequence1 is equal to sequence2."
| _ -> failwith("Invalid comparison result.")
在前面的代码中,仅计算并检查第一个元素,结果为 -1。
Seq.countBy 采用一个函数,该函数为每个元素生成一个名为 键 的值。 通过在每个元素上调用此函数,为每个元素生成一个键。
Seq.countBy
然后返回一个包含键值的序列,以及生成每个键值的元素数的计数。
let mySeq1 = seq { 1.. 100 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
let seqResult =
mySeq1
|> Seq.countBy (fun elem ->
if elem % 3 = 0 then 0
elif elem % 3 = 1 then 1
else 2)
printSeq seqResult
输出如下所示。
(1, 34) (2, 33) (0, 33)
上一个输出显示,原始序列中有 34 个元素生成了键 1、33 个生成键 2 和 33 个生成键 0 的值。
可以通过调用 Seq.groupBy 对序列的元素进行分组。
Seq.groupBy
获取一个序列和一个从元素生成键的函数。 该函数在序列的每个元素上执行。
Seq.groupBy
返回元组序列,其中每个元组的第一个元素是键,第二个元素是生成该键的元素序列。
下面的代码示例演示如何将 Seq.groupBy
数字序列从 1 到 100 划分为三个组,这些组具有非重复键值 0、1 和 2。
let sequence = seq { 1 .. 100 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
let sequences3 =
sequences
|> Seq.groupBy (fun index ->
if (index % 3 = 0) then 0
elif (index % 3 = 1) then 1
else 2)
sequences3 |> printSeq
输出如下所示。
(1, seq [1; 4; 7; 10; ...]) (2, seq [2; 5; 8; 11; ...]) (0, seq [3; 6; 9; 12; ...])
可以通过调用 Seq.distinct 来创建消除重复元素的序列。 或者,可以使用 Seq.distinctBy,该函数对每个元素调用键生成函数。 生成的序列包含具有唯一键的原始序列的元素;将丢弃生成早期元素的重复键的后续元素。
下面的代码示例演示了 Seq.distinct
如何使用 。
Seq.distinct
通过生成表示二进制数的序列,然后显示唯一的非重复元素为 0 和 1 来演示。
let binary n =
let rec generateBinary n =
if (n / 2 = 0) then [n]
else (n % 2) :: generateBinary (n / 2)
generateBinary n
|> List.rev
|> Seq.ofList
printfn "%A" (binary 1024)
let resultSequence = Seq.distinct (binary 1024)
printfn "%A" resultSequence
以下代码演示 Seq.distinctBy
了从包含负数和正数的序列开始,并使用绝对值函数作为键生成函数。 生成的序列缺少与序列中负数相对应的所有正数,因为负数出现在序列前面,因此被选中,而不是具有相同绝对值或键的正数。
let inputSequence = { -5 .. 10 }
let printSeq seq1 = Seq.iter (printf "%A ") seq1
printfn "Original sequence: "
printSeq inputSequence
printfn "\nSequence with distinct absolute values: "
let seqDistinctAbsoluteValue = Seq.distinctBy (fun elem -> abs elem) inputSequence
printSeq seqDistinctAbsoluteValue
只读序列和缓存序列
Seq.readonly 创建序列的只读副本。
Seq.readonly
如果具有读写集合(如数组),并且不想修改原始集合,则非常有用。 此函数可用于保留数据封装。 在以下代码示例中,将创建包含数组的类型。 属性公开数组,但不是返回数组,而是使用 <
type ArrayContainer(start, finish) =
let internalArray = [| start .. finish |]
member this.RangeSeq = Seq.readonly internalArray
member this.RangeArray = internalArray
let newArray = new ArrayContainer(1, 10)
let rangeSeq = newArray.RangeSeq
let rangeArray = newArray.RangeArray
// These lines produce an error:
//let myArray = rangeSeq :> int array
//myArray[0] <- 0
// The following line does not produce an error.
// It does not preserve encapsulation.
rangeArray[0] <- 0
Seq.cache 创建序列的存储版本。 用于 Seq.cache
避免重新评估序列,或者如果有多个线程使用序列,但必须确保每个元素只执行一次。 如果序列由多个线程使用,则可以有一个线程来枚举和计算原始序列的值,其余线程可以使用缓存序列。
对序列执行计算
简单的算术运算类似于列表,例如 Seq.average、 Seq.sum、 Seq.averageBy、 Seq.sumBy 等。
Seq.fold、 Seq.reduce 和 Seq.scan 类似于可用于列表的相应函数。 序列支持列出支持的这些函数的完整变体的子集。 有关详细信息和示例,请参阅 列表。