F# 序列
最后修改日期:2025 年 5 月 17 日
在本全面指南中,我们将深入探讨 F# 中的序列——这是该语言中最强大和最基础的集合类型之一。序列提供惰性求值、无限数据结构以及对大型数据集的高效处理。
F# 中的序列是同一类型元素的逻辑序列。当您拥有大量有序数据集合但不必使用所有元素时,序列特别有用。与列表和数组不同,序列是惰性求值的,这意味着元素仅在需要时才计算。这使得序列成为处理大型或无限数据集的理想选择。
F# 序列基础
在第一个示例中,我们使用范围运算符创建了一个简单的序列。我们还可以使用 `seq` 关键字创建显式序列。序列可以从数组、列表或其他集合创建。`Seq` 模块提供了用于操作和消费序列的函数。
// Range sequence let numbers = seq { 1..5 } printfn "Range sequence: %A" numbers // Explicit sequence let colors = seq { "red"; "green"; "blue" } printfn "Color sequence: %A" colors // Sequence from array let fromArray = [| 1; 2; 3 |] |> Seq.ofArray printfn "From array: %A" fromArray // Consuming sequences seq { 1..3 } |> Seq.iter (printfn "Number: %d") // Converting to list let numberList = seq { 1..5 } |> Seq.toList printfn "As list: %A" numberList
在上面的示例中,我们使用范围运算符创建了一个从 1 到 5 的数字序列。我们还创建了一个颜色序列,并将一个数组转换为序列。`Seq.iter` 函数用于消费序列,打印每个数字。最后,我们使用 `Seq.toList` 将序列转换为列表。
λ dotnet fsi basics.fsx Range sequence: seq [1; 2; 3; 4; ...] Color sequence: seq ["red"; "green"; "blue"] From array: seq [1; 2; 3] Number: 1 Number: 2 Number: 3 As list: [1; 2; 3; 4; 5]
F# 序列表达式
F# 序列表达式是使用推导式生成序列的强大方法。它们允许您使用 `for`、`if` 和 `yield` 关键字的组合来创建序列。您可以使用序列表达式来创建具有过滤和转换逻辑的复杂序列。
// Simple sequence expression let squares = seq { for n in 1..5 do yield n * n } printfn "Squares: %A" (squares |> Seq.toList) // Sequence with filtering let evens = seq { for n in 1..10 do if n % 2 = 0 then yield n } printfn "Evens: %A" (evens |> Seq.toList) // Nested sequence expression let coordinates = seq { for x in 1..3 do for y in 1..3 do yield (x, y) } printfn "Coordinates: %A" (coordinates |> Seq.toList) // Yield bang (concatenates sequences) let combined = seq { yield! seq { 1..3 } yield! seq { 4..6 } } printfn "Combined: %A" (combined |> Seq.toList)
在上面的示例中,我们使用简单的序列表达式创建了一个平方数序列。我们还使用过滤条件创建了一个偶数序列。嵌套序列表达式在 2D 网格中生成坐标。最后,我们使用 `yield!` 关键字将两个序列连接成一个。
λ dotnet fsi expressions.fsx Squares: [1; 4; 9; 16; 25] Evens: [2; 4; 6; 8; 10] Coordinates: [(1, 1); (1, 2); (1, 3); (2, 1); (2, 2); (2, 3); (3, 1); (3, 2); (3, 3)] Combined: [1; 2; 3; 4; 5; 6]
F# 无限序列
无限序列是 F# 的一项强大功能。它们允许您处理潜在的无界数据,而无需一次性为所有元素消耗内存。
// Infinite sequence of natural numbers let naturals = Seq.initInfinite (fun i -> i + 1) printfn "First 5 naturals: %A" (naturals |> Seq.take 5 |> Seq.toList) // Infinite Fibonacci sequence let rec fibs = seq { yield 0 yield 1 yield! (Seq.zip fibs (Seq.skip 1 fibs) |> Seq.map (fun (a,b) -> a + b)) } printfn "First 10 Fibonacci: %A" (fibs |> Seq.take 10 |> Seq.toList) // Infinite random numbers let rnd = System.Random() let randoms = seq { while true do yield rnd.Next(1, 100) } printfn "Random numbers: %A" (randoms |> Seq.take 5 |> Seq.toList) // Infinite repeating sequence let repeating = seq { while true do yield! [1; 2; 3] } printfn "Repeating: %A" (repeating |> Seq.take 7 |> Seq.toList)
可以使用 `Seq.initInfinite` 函数创建无限序列。斐波那契数列是使用递归和惰性求值生成的。随机数生成器创建了一个无限的随机数序列。重复序列演示了如何创建无限重复有限列表的序列。
λ dotnet fsi infinite.fsx First 5 naturals: [1; 2; 3; 4; 5] First 10 Fibonacci: [0; 1; 1; 2; 3; 5; 8; 13; 21; 34] Random numbers: [42; 17; 89; 3; 76] Repeating: [1; 2; 3; 1; 2; 3; 1]
F# 序列操作
F# 提供了丰富的序列操作集。这些操作包括过滤、映射、排序和聚合。
let numbers = seq { 1..10 } // Filtering let evens = numbers |> Seq.filter (fun x -> x % 2 = 0) printfn "Evens: %A" (evens |> Seq.toList) // Mapping let squares = numbers |> Seq.map (fun x -> x * x) printfn "Squares: %A" (squares |> Seq.toList) // Sorting let sorted = numbers |> Seq.sortDescending printfn "Sorted descending: %A" (sorted |> Seq.toList) // Aggregation let sum = numbers |> Seq.sum printfn "Sum: %d" sum // Chunking let chunks = numbers |> Seq.chunkBySize 3 printfn "Chunks: %A" (chunks |> Seq.toList) // Pairwise let pairs = numbers |> Seq.pairwise printfn "Pairs: %A" (pairs |> Seq.toList) // Windowed let windows = numbers |> Seq.windowed 3 printfn "Windows: %A" (windows |> Seq.take 3 |> Seq.toList)
该示例演示了序列上的各种操作。
λ dotnet fsi operations.fsx Evens: [2; 4; 6; 8; 10] Squares: [1; 4; 9; 16; 25; 36; 49; 64; 81; 100] Sorted descending: [10; 9; 8; 7; 6; 5; 4; 3; 2; 1] Sum: 55 Chunks: [[|1; 2; 3|]; [|4; 5; 6|]; [|7; 8; 9|]; [|10|]] Pairs: [(1, 2); (2, 3); (3, 4); (4, 5); (5, 6); (6, 7); (7, 8); (8, 9); (9, 10)] Windows: [[|1; 2; 3|]; [|2; 3; 4|]; [|3; 4; 5|]]
F# 序列性能
序列与列表和数组相比的性能特征很重要。序列是惰性的,并且可能比列表和数组更节省内存。但是,由于惰性求值,它们可能存在性能开销。
为了准确比较性能,列表和序列的求值被放在单独的文件中,并进行独立计时。`list_operations.fsx` 文件对列表进行急切求值,而 `sequence_operations.fsx` 测量惰性序列执行。
#time "on" open System.Diagnostics let stopwatch = Stopwatch.StartNew() let listData = [1L..1000000L] let listProcessed = listData |> List.map (fun x -> x * x) let listSum = listProcessed |> List.sum stopwatch.Stop() printfn "List sum: %d (Time: %A ms)" listSum stopwatch.ElapsedMilliseconds #time "off"
`list_operations.fsx` 文件演示了列表的急切求值。它创建了一个从 1 到 1,000,000 的数字列表,通过对每个数字进行平方来处理它们,并计算总和。执行时间使用秒表测量。
#time "on" open System.Diagnostics let stopwatch = Stopwatch.StartNew() let seqData = seq {1L..1000000L} let seqProcessed = seqData |> Seq.map (fun x -> x * x) let seqSum = seqProcessed |> Seq.sum stopwatch.Stop() printfn "Sequence sum: %d (Time: %d ms)" seqSum stopwatch.ElapsedMilliseconds #time "off"
`sequence_operations.fsx` 文件演示了序列的惰性求值。它创建了一个从 1 到 1,000,000 的数字序列,通过对每个数字进行平方来处理它们,并计算总和。
λ dotnet fsi list_operations.fsx List sum: 333333833333500000 (Time: 83 ms) Real: 00:00:00.089, CPU: 00:00:00.171, GC gen0: 1, gen1: 0, gen2: 0 λ dotnet fsi sequence_operations.fsx Sequence sum: 333333833333500000 (Time: 16 ms) Real: 00:00:00.022, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
F# 序列模块
`Seq` 模块提供了各种用于处理序列的函数,包括过滤、映射、折叠等。这些函数旨在高效,可用于处理大型数据集,而无需一次性将它们全部加载到内存中。
let numbers = seq { 1..10 } // Seq.exists - checks if any element satisfies condition let hasEven = numbers |> Seq.exists (fun x -> x % 2 = 0) printfn "Has even numbers: %b" hasEven // Seq.fold - accumulation let product = numbers |> Seq.fold (fun acc x -> acc * x) 1 printfn "Product: %d" product // Seq.groupBy - grouping elements let grouped = numbers |> Seq.groupBy (fun x -> x % 3) printfn "Grouped by mod 3: %A" (grouped |> Seq.toList) // Seq.distinct - removing duplicates let withDups = seq { 1; 2; 2; 3; 4; 4; 4; 5 } let distinct = withDups |> Seq.distinct printfn "Distinct: %A" (distinct |> Seq.toList) // Seq.skip/take - pagination let page1 = numbers |> Seq.skip 0 |> Seq.take 3 let page2 = numbers |> Seq.skip 3 |> Seq.take 3 printfn "Page 1: %A" (page1 |> Seq.toList) printfn "Page 2: %A" (page2 |> Seq.toList)
在此示例中,我们使用 `Seq` 模块演示了序列上的各种操作。`Seq.exists` 函数检查序列中的任何元素是否满足给定条件。`Seq.fold` 函数累积序列中的值,允许我们计算所有元素的乘积。`Seq.groupBy` 函数根据给定键对元素进行分组,而 `Seq.distinct` 移除序列中的重复项。最后,我们使用 `Seq.skip` 和 `Seq.take` 来实现分页,允许我们从序列中检索特定页面的元素。
λ dotnet fsi modules.fsx Has even numbers: true Product: 3628800 Grouped by mod 3: [(1, seq [1; 4; 7; 10]); (2, seq [2; 5; 8]); (0, seq [3; 6; 9])] Distinct: [1; 2; 3; 4; 5] Page 1: [1; 2; 3] Page 2: [4; 5; 6]
在本全面指南中,我们深入探讨了 F# 序列——从基本创建到高级技术。序列是 F# 中用于高效处理大型、可能无限数据集的强大工具。