F# 列表推导式
最后修改日期:2025 年 5 月 17 日
在本文中,我们将探讨 F# 中的列表推导式——一种通过转换和过滤现有集合来生成列表的简洁而强大的方法。
F# 中的 列表推导式 是一种基于现有列表或序列创建列表的语法结构。通过在列表表达式中使用 yield
关键字,我们可以通过指定要包含的元素来生成新列表,通常带有过滤条件和转换。列表推导式提供了一种声明式的方式来处理集合,这种方式既易读又富有表现力。
基本列表推导式
列表推导式是 F# 中一项强大的功能,它允许您从现有列表或序列创建列表。它们提供了一种简洁的语法,通过应用转换和过滤条件来生成新列表。
// 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]
过滤条件
列表推导式还可以包含过滤条件,以从源列表中选择特定元素。这使您能够创建满足特定条件的新列表。
// 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]
嵌套列表推导式
嵌套列表推导式允许您创建列表的列表或执行笛卡尔积。它们还可以用于展平嵌套列表。
// 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# 中的序列表达式进行比较。列表推导式会立即创建具体的列表,而序列表达式则惰性生成序列。这意味着序列的元素是按需计算的,这在使用大型数据集时可以带来性能优势。
#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# 中一项强大的功能,它允许您解构和分析数据类型。它可以在列表推导式中使用,以根据其结构过滤和转换元素。
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
该代码定义了一个具有三个构造函数(Circle
、Rectangle
和 Triangle
)的区分联合类型 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 从列表中过滤正数。
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
语句会在输出列表中生成一项。
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!
关键字用于将集合的所有元素添加到结果列表中。它会展平集合,将每个元素单独插入到输出列表中。
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# 列表推导式的强大功能和灵活性。它们提供了一种简洁、声明式的方式来创建和转换列表,既易读又富有表现力。