ZetCode

F# 序列

最后修改日期:2025 年 5 月 17 日

在本全面指南中,我们将深入探讨 F# 中的序列——这是该语言中最强大和最基础的集合类型之一。序列提供惰性求值、无限数据结构以及对大型数据集的高效处理。

F# 中的序列是同一类型元素的逻辑序列。当您拥有大量有序数据集合但不必使用所有元素时,序列特别有用。与列表和数组不同,序列是惰性求值的,这意味着元素仅在需要时才计算。这使得序列成为处理大型或无限数据集的理想选择。

F# 序列基础

在第一个示例中,我们使用范围运算符创建了一个简单的序列。我们还可以使用 `seq` 关键字创建显式序列。序列可以从数组、列表或其他集合创建。`Seq` 模块提供了用于操作和消费序列的函数。

basics.fsx
// 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` 关键字的组合来创建序列。您可以使用序列表达式来创建具有过滤和转换逻辑的复杂序列。

expressions.fsx
// 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.fsx
// 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# 提供了丰富的序列操作集。这些操作包括过滤、映射、排序和聚合。

operations.fsx
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` 测量惰性序列执行。

list_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 的数字列表,通过对每个数字进行平方来处理它们,并计算总和。执行时间使用秒表测量。

sequence_operations.fsx
#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` 模块提供了各种用于处理序列的函数,包括过滤、映射、折叠等。这些函数旨在高效,可用于处理大型数据集,而无需一次性将它们全部加载到内存中。

modules.fsx
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# 中用于高效处理大型、可能无限数据集的强大工具。

作者

我的名字是 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。