ZetCode

F# 列表推导式

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

在本文中,我们将探讨 F# 中的列表推导式——一种通过转换和过滤现有集合来生成列表的简洁而强大的方法。

F# 中的 列表推导式 是一种基于现有列表或序列创建列表的语法结构。通过在列表表达式中使用 yield 关键字,我们可以通过指定要包含的元素来生成新列表,通常带有过滤条件和转换。列表推导式提供了一种声明式的方式来处理集合,这种方式既易读又富有表现力。

基本列表推导式

列表推导式是 F# 中一项强大的功能,它允许您从现有列表或序列创建列表。它们提供了一种简洁的语法,通过应用转换和过滤条件来生成新列表。

basic.fsx
// Simple range comprehension
let numbers = [1..5]
printfn "Range: %A" numbers

// Explicit list comprehension
let colors = ["red"; "green"; "blue"]
printfn "Colors: %A" colors

// Comprehension with yield
let squares = [for x in 1..5 do yield x * x]
printfn "Squares: %A" squares

// Inline computation
let cubes = [for x in 1..3 -> x * x * x]
printfn "Cubes: %A" cubes

此代码演示了 F# 中的基本列表推导式。第一行使用范围运算符创建了一个从 1 到 5 的数字列表。第二行创建了一个颜色列表。第三行使用列表推导式生成了从 1 到 5 的数字的平方。第四行使用内联计算创建了一个立方体列表。

λ dotnet fsi basic.fsx
Range: [1; 2; 3; 4; 5]
Colors: ["red"; "green"; "blue"]
Squares: [1; 4; 9; 16; 25]
Cubes: [1; 8; 27]

过滤条件

列表推导式还可以包含过滤条件,以从源列表中选择特定元素。这使您能够创建满足特定条件的新列表。

filtering.fsx
// Filter even numbers
let evens = [for x in 1..10 do if x % 2 = 0 then yield x]
printfn "Evens: %A" evens

// Multiple conditions
let filtered = [
    for x in 1..20 do
        if x % 2 = 0 && x % 3 = 0 then
            yield x
]
printfn "Divisible by 2 and 3: %A" filtered

// Using when guard
let primes = [
    for x in 2..20 do
        if [for d in 2..x/2 do if x % d = 0 then yield d] = [] then
            yield x
]
printfn "Primes: %A" primes

此代码演示了列表推导式中的过滤。在第一个推导式中,我们过滤了从 1 到 10 的偶数。在第二个推导式中,我们过滤了能同时被 2 和 3 整除的数字。第三个推导式使用保护子句来过滤从 2 到 20 的素数。

λ dotnet fsi filtering.fsx
Evens: [2; 4; 6; 8; 10]
Divisible by 2 and 3: [6; 12; 18]
Primes: [2; 3; 5; 7; 11; 13; 17; 19]

嵌套列表推导式

嵌套列表推导式允许您创建列表的列表或执行笛卡尔积。它们还可以用于展平嵌套列表。

nested.fsx
// Cartesian product
let product = [
    for x in 1..3 do
        for y in 1..3 do
            yield (x, y)
]
printfn "Cartesian product: %A" product

// Nested with filtering
let pythagoreanTriples = [
    for a in 1..10 do
        for b in a..10 do
            for c in b..10 do
                if a*a + b*b = c*c then
                    yield (a, b, c)
]
printfn "Pythagorean triples: %A" pythagoreanTriples

// Flattening nested lists
let matrix = [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]]
let flattened = [for row in matrix do for num in row do yield num]
printfn "Flattened matrix: %A" flattened

该示例展示了如何在列表推导式中使用多个生成器。第一个推导式创建了两个范围的笛卡尔积。第二个推导式通过检查 a2 + b2 = c2 的条件来生成勾股数。第三个推导式将嵌套列表(矩阵)展平为单个列表。

λ dotnet fsi nested.fsx
Cartesian product: [(1, 1); (1, 2); (1, 3); (2, 1); (2, 2); (2, 3); (3, 1); (3, 2); (3, 3)]
Pythagorean triples: [(3, 4, 5); (6, 8, 10)]
Flattened matrix: [1; 2; 3; 4; 5; 6; 7; 8; 9]

F# 列表推导式与序列表达式的比较

可以将列表推导式与 F# 中的序列表达式进行比较。列表推导式会立即创建具体的列表,而序列表达式则惰性生成序列。这意味着序列的元素是按需计算的,这在使用大型数据集时可以带来性能优势。

comparison.fsx
#time "on"
// List comprehension (eager evaluation)
let listSquares = [for x in 1L..1000000L -> x * x]
printfn "List length: %d" (List.length listSquares)

let listSum = [for x in 1L..1000000L -> x * x] |> List.sum
printfn "List sum: %d" listSum
#time "off"

#time "on"

// Sequence expression (lazy evaluation)
let seqSquares = seq {for x in 1L..1000000L -> x * x}
printfn "Sequence length: %d" (seqSquares |> Seq.length)

let seqSum = seq {for x in 1L..1000000L -> x * x} |> Seq.sum
printfn "Sequence sum: %d" seqSum

#time "off"

该示例演示了列表推导式和序列推导式之间的区别。第一部分使用列表推导式创建了一个平方列表,并计算了平方的和。第二部分使用序列表达式创建了一个平方序列,并计算了平方的和。#time 指令用于测量每个操作所需的时间。列表推导式会急切地评估整个列表,而序列表达式则会根据需要惰性地评估元素。这可能会导致性能差异,尤其是在处理大型数据集时。

λ dotnet fsi comparison.fsx
List length: 1000000
List sum: 333333833333500000
Real: 00:00:00.084, CPU: 00:00:00.125, GC gen0: 1, gen1: 0, gen2: 0
Sequence length: 1000000
Sequence sum: 333333833333500000
Real: 00:00:00.098, CPU: 00:00:00.093, GC gen0: 0, gen1: 0, gen2: 0

使用模式匹配

模式匹配是 F# 中一项强大的功能,它允许您解构和分析数据类型。它可以在列表推导式中使用,以根据其结构过滤和转换元素。

pattern_matching.fsx
type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float
    | Triangle of _base: float * height: float

let shapes = [
    Circle 5.0
    Rectangle (4.0, 6.0)
    Triangle (3.0, 4.0)
    Circle 2.5
    Rectangle (5.0, 5.0)
]

// Filter and extract using pattern matching
let circles = [
    for shape in shapes do
        match shape with
        | Circle r -> yield r
        | _ -> ()
]
printfn "Circle radii: %A" circles

// Transform with pattern matching
let areas = [
    for shape in shapes do
        match shape with
        | Circle r -> yield System.Math.PI * r * r
        | Rectangle (w, h) -> yield w * h
        | Triangle (b, h) -> yield 0.5 * b * h
]
printfn "Areas: %A" areas

该代码定义了一个具有三个构造函数(CircleRectangleTriangle)的区分联合类型 Shape。它创建了一个形状列表,并演示了如何在列表推导式中使用模式匹配。第一个推导式过滤并提取圆的半径,而第二个推导式使用模式匹配计算不同形状的面积。

λ dotnet fsi pattern_matching.fsx
Circle radii: [5.0; 2.5]
Areas: [78.53981634; 24.0; 6.0; 19.63495408; 25.0]

使用 lambda 表达式

您可以在列表推导式中使用 lambda 表达式来内联定义自定义过滤或转换逻辑。此示例演示了如何使用 lambda 从列表中过滤正数。

lambda_in_comprehension.fsx
let vals = [ 1; -2; -3; 4; 5 ]

[ for v in vals do
      let f = fun e -> e > 0
      if f(v) then yield v ] |> printfn "%A"

在这里,在推导式中定义了一个 lambda 表达式 fun e -> e > 0 来检查值是否为正。只有正值才会被生成到结果列表中。

λ dotnet fsi lambda_in_comprehension.fsx
[1; 4; 5]

yield 关键字

yield 关键字用于列表推导式,将单个元素添加到结果列表中。每个 yield 语句会在输出列表中生成一项。

yield_example.fsx
let chars = [ 'a' .. 'z' ]

let res2 = [for e in chars do yield [e; e; e] ]
printfn "%A" res2

let words = [ "sky"; "cloud"; "park"; "rock"; "war" ]

let res = [ for e in words do yield e ]
printfn "%A" res

在第一个示例中,yield 为字母表中的每个字符添加了三个重复字符的列表,从而生成了一个列表的列表。在第二个示例中,yield 将每个单词作为单个元素添加到结果列表中。

λ dotnet fsi yield_example.fsx
[[a; a; a]; [b; b; b]; ...; [z; z; z]]
["sky"; "cloud"; "park"; "rock"; "war"]

yield! 关键字

yield! 关键字用于将集合的所有元素添加到结果列表中。它会展平集合,将每个元素单独插入到输出列表中。

yield_bang_example.fsx
let res =
    [ for a in 1..5 do
          yield! [ a .. a + 3 ] ]

printfn "%A" res

let words = [ "sky"; "cloud"; "park"; "rock"; "war" ]

let res2 = [ for e in words do yield! e ]
printfn "%A" res2

在第一个示例中,yield! 为范围中的每个 a 插入子列表 [a .. a + 3] 的所有元素,从而生成一个展平的数字列表。在第二个示例中,yield! 将每个单词中的每个字符插入到结果列表中,从而生成一个包含所有单词中所有字符的列表。

λ dotnet fsi yield_bang_example.fsx
[1; 2; 3; 4; 2; 3; 4; 5; 3; 4; 5; 6; 4; 5; 6; 7; 5; 6; 7; 8]
['s'; 'k'; 'y'; 'c'; 'l'; 'o'; 'u'; 'd'; 'p'; 'a'; 'r'; 'k'; 'r'; 'o'; 'c'; 'k'; 'w'; 'a'; 'r']

在本文中,我们探讨了 F# 列表推导式的强大功能和灵活性。它们提供了一种简洁、声明式的方式来创建和转换列表,既易读又富有表现力。

作者

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