ZetCode

F# 列表

最后修改时间 2025 年 5 月 1 日

本文演示了如何使用 F# 中的 List 集合。

F# 中的 列表 是一个有序的、不可变的、所有元素类型相同的元素序列。列表是函数式编程的基础,它提供了一种方便的方式来处理有序集合,而无需修改其内容。

可以使用列表字面量创建列表,其中元素用分号分隔并包含在方括号中。

let vals = [ 1; 2; 3; 4; 5 ]

或者,F# 允许一种更易读的语法,其中省略了分号,每个元素都放在方括号内的新行上。

let vals = [
    1
    2
    3
    4
    5
]

两种语法都是有效的,但第二种格式提高了可读性,特别是对于较长的列表。由于 F# 中的列表是不可变的,任何修改都需要创建新列表,而不是更改现有列表。

F# 中的列表支持各种操作,包括过滤、映射和折叠,这使得开发人员可以高效地处理数据。像 List.map 这样的函数将转换应用于集合中的每个元素,而 List.filter 则根据特定标准选择元素。这些操作通过强调使用纯函数来修改数据,从而拥抱了函数式编程的原则。

此外,列表可以递归处理,使其适用于需要迭代但不需要传统循环的算法。不变性、内置列表函数和模式匹配的结合使得 F# 列表成为处理有序集合的强大工具。

F# 列表简单示例

下面是一个简单的列表示例。

main.fsx
let vals = [ 1; 2; 3; 4; 5; 6 ]

printfn "%A" vals
printfn "%d" vals.Head
printfn "%d" vals.Length
printfn "%A" vals.Tail

我们有一个整数列表。我们打印列表的内容、它的头、大小和尾。

let vals = [ 1; 2; 3; 4; 5; 6 ]

我们使用数组字面量定义了一个整数列表。

printfn "%A" vals

使用 %A 格式说明符,我们可以漂亮地打印列表。

printfn "%d" vals.Head

使用 Head 属性,我们打印列表的第一个元素。

printfn "%d" vals.Length

我们使用 Length 获取列表的大小。

printfn "%A" vals.Tail

使用 Tail,我们获得除第一个元素之外的所有列表元素。

λ dotnet fsi main.fsx
[1; 2; 3; 4; 5; 6]
1
6
[2; 3; 4; 5; 6]

F# 列表迭代

在下一个示例中,我们将遍历列表的元素。

main.fsx
let vals = [ 1; 2; 3; 4; 5; 6 ]

vals |> List.iter (printfn "%d")

printfn "------------------------"

for e in vals do
    printfn "%d" e

我们提供了两种基本的迭代方式。

vals |> List.iter (printfn "%d")

List.iter 是遍历列表元素的函数式方法。

for e in vals do
    printfn "%d" e

经典的命令式方法是通过 for 循环。

λ dotnet fsi main.fsx
1
2
3
4
5
6
------------------------
1
2
3
4
5
6

F# 列表索引

列表元素通过其索引进行访问。

main.fsx
let words = ["pen"; "cup"; "dog"; "person";
    "cement"; "coal"; "spectacles"; "cup"; "bread"]

let w1 = List.item 1 words
printfn "%s" w1

let w2 = words[0]
printfn "%s" w2

let i1 = List.findIndex(fun e -> e = "cup") words
printfn $"The first index of cup is {i1}"

let i2 = List.findIndexBack(fun e -> e = "cup") words
printfn $"The last index of cup is {i2}"

程序包含列表索引操作。

let w1 = List.item 1 words

我们使用 List.item 获取列表的第二个元素。

let w2 = words[0]

我们也可以使用经典的 C 风格语法。

let i1 = List.findIndex(fun e -> e = "cup") words

使用 List.findIndex,我们找到满足给定谓词函数的第一个元素。

let i2 = List.findIndexBack(fun e -> e = "cup") words

使用 List.findIndexBack,我们找到满足给定谓词函数的最后一个元素。

λ dotnet fsi main.fsx
cup
pen
The first index of cup is 1
The last index of cup is 7

F# List.map

List.map 函数将给定函数应用于集合的每个元素。

main.fsx
let vals = [1..10]

let res = List.map(fun e -> e * 2) vals
printfn "%A" res

我们在整数列表上应用了一个 map 函数。

let vals = [ 1 .. 10]

我们使用范围运算符定义了一个列表。

let res = List.map(fun e -> e * 2) vals

列表的每个元素都乘以 2。结果被赋给 res 变量。

λ dotnet fsi main.fsx
[2; 4; 6; 8; 10; 12; 14; 16; 18; 20]

F# List.filter

我们可以使用 List.filter 过滤列表元素。

main.fsx
let vals = [-3; -2; 0; 1; -5; 7; 9]
let words = ["sky"; "war"; "rock"; "ocean"; "cloud"; "water"]

let pos = List.filter(fun e -> e > 0) vals
printfn "%A" pos

let res = List.filter(fun (e:string) -> e.StartsWith("w")) words
printfn "%A" res

在程序中,我们找出整数列表中所有的正数,以及单词列表中所有以 'w' 开头的字符串。

let pos = List.filter(fun e -> e > 0) vals

List.filter 函数接受一个谓词函数。所有元素都必须满足给定的谓词。

let res = List.filter(fun (e:string) -> e.StartsWith("w")) words

有时,需要使用显式类型定义来帮助编译器。

λ dotnet fsi main.fsx
[1; 7; 9]
["war"; "water"]

List.zip

List.zip 函数将两个列表合并为一个对的列表。两个列表必须具有相同的长度。

main.fsx
let words = ["sky"; "cup"; "rock"; "pen"; "pearl"; "cloud"]
let n = words.Length
let idxs = [1..n]

let data = List.zip idxs words
printfn "%A" data

printfn "-----------------"

let m = data |> Map.ofList
m |> Map.iter (fun k v -> printfn $"{k}: {v}");

我们将字符串列表与整数列表合并。然后我们将列表转换为一个 map。

λ dotnet fsi main.fsx
[(1, "sky"); (2, "cup"); (3, "rock"); (4, "pen"); (5, "pearl"); (6, "cloud")]
-----------------
1: sky
2: cup
3: rock
4: pen
5: pearl
6: cloud

F# 合并列表

使用 @ 运算符,我们可以合并两个列表。

main.fsx
let a = [1; 2; 3; 4]
let b = [4; 4; 5; 6]

let merged = a @ b |> List.distinct
printfn "%A" merged

let merged2 = a @ b
printfn "%A" merged2

程序合并了两个列表。

let merged = a @ b |> List.distinct

我们合并了两个列表,并将值传递给 List.distinct,它会删除重复项。

let merged2 = a @ b

我们合并了两个列表;我们拥有所有值,包括重复项。

λ dotnet fsi main.fsx
[1; 2; 3; 4; 5; 6]
[1; 2; 3; 4; 4; 4; 5; 6]

F# 排序整数列表

在下一个示例中,我们对整数进行排序。

main.fsx
let nums = [ -1; 6; -2; 3; 0; -4; 5; 1; 2 ]

nums |> List.sort |> printfn "%A"
nums |> List.sortDescending |> printfn "%A"

nums |> List.sortBy (abs) |> printfn "%A"
nums |> List.sortByDescending (abs) |> printfn "%A"

我们有一个整数列表。我们使用 List.sortList.sortDescending 对它们进行排序。

nums |> List.sortBy (abs) |> printfn "%A"
nums |> List.sortByDescending (abs) |> printfn "%A"

借助 abs,我们可以不考虑符号对整数进行排序。

λ dotnet fsi main.fsx
[-4; -2; -1; 0; 1; 2; 3; 5; 6]
[6; 5; 3; 2; 1; 0; -1; -2; -4]
[0; -1; 1; -2; 2; 3; -4; 5; 6]
[6; 5; -4; 3; -2; 2; -1; 1; 0]

F# 排序记录列表

在下一个示例中,我们对记录列表进行排序。

main.fsx
type User =
    { Name: string
      Occupation: string
      Salary: int }

let users =
    [ { Name = "John Doe"
        Occupation = "gardener"
        Salary = 1280 }
      { Name = "Roger Roe"
        Occupation = "driver"
        Salary = 860 }
      { Name = "Tom Brown"
        Occupation = "shopkeeper"
        Salary = 990 } ]

users
|> List.sortBy (fun u -> u.Salary)
|> List.iter (fun u -> printfn "%A" u)

printfn "--------------------------------"

users
|> List.sortByDescending (fun u -> u.Occupation)
|> List.iter (fun u -> printfn "%A" u)

程序包含一个 User 记录列表。我们按薪水和职业对用户进行排序。

users
|> List.sortBy (fun u -> u.Salary)
|> List.iter (fun u -> printfn "%A" u)

使用 List.sortBy,用户按薪水升序排序。

users
|> List.sortByDescending (fun u -> u.Occupation)
|> List.iter (fun u -> printfn "%A" u)

这里,用户使用 sortByDescending 按职业降序排序。

λ dotnet fsi main.fsx
{ Name = "Roger Roe"
  Occupation = "driver"
  Salary = 860 }
{ Name = "Tom Brown"
  Occupation = "shopkeeper"
  Salary = 990 }
{ Name = "John Doe"
  Occupation = "gardener"
  Salary = 1280 }
--------------------------------
{ Name = "Tom Brown"
  Occupation = "shopkeeper"
  Salary = 990 }
{ Name = "John Doe"
  Occupation = "gardener"
  Salary = 1280 }
{ Name = "Roger Roe"
  Occupation = "driver"
  Salary = 860 }

F# 列表推导式

列表推导式是一种生成列表的强大语法。列表推导式提供了一种创建列表的简洁方法。

在 F# 中,我们可以使用范围和生成器创建列表推导式。

main.fsx
let vals = [ -1; 0; 2; -2; 1; 3; 4; -6 ]

let pos =
    [ for e in vals do
          if e > 0 then yield e ]

printfn "%A" pos

printfn "---------------------------------"

[ for e in 1 .. 100 -> e * e ] |> printfn "%A"


printfn "---------------------------------"

[ for a in 1 .. 100 do
    if a % 3 = 0 && a % 5 = 0 then yield a] |> printfn "%A"

printfn "---------------------------------"

let vals3 =
    [ for x in 1 .. 3 do
          for y in 1 .. 10 -> x, y ]

printfn "%A" vals3

在 F# 中,列表推导式使用 for 循环、if 条件和 yield 关键字。

let vals = [ -1; 0; 2; -2; 1; 3; 4; -6 ]

let pos =
    [ for e in vals do
            if e > 0 then yield e ]

我们有一个值列表。使用列表推导式构建了一个新列表。它只包含正值。

[ for e in 1 .. 100 -> e * e ] |> printfn "%A"

我们可以在列表推导式中使用范围。

[ for a in 1 .. 100 do
    if a % 3 = 0 && a % 5 = 0 then yield a] |> printfn "%A"

这里我们使用了两个 if 条件。

let vals3 =
    [ for x in 1 .. 3 do
          for y in 1 .. 10 -> x, y ]

此外,也可以使用两个 for 循环。

λ dotnet fsi main.fsx
[2; 1; 3; 4]
---------------------------------
[1; 4; 9; 16; 25; 36; 49; 64; 81; 100; 121; 144; 169; 196; 225; 256; 289; 324;
 361; 400; 441; 484; 529; 576; 625; 676; 729; 784; 841; 900; 961; 1024; 1089;
 1156; 1225; 1296; 1369; 1444; 1521; 1600; 1681; 1764; 1849; 1936; 2025; 2116;
 2209; 2304; 2401; 2500; 2601; 2704; 2809; 2916; 3025; 3136; 3249; 3364; 3481;
 3600; 3721; 3844; 3969; 4096; 4225; 4356; 4489; 4624; 4761; 4900; 5041; 5184;
 5329; 5476; 5625; 5776; 5929; 6084; 6241; 6400; 6561; 6724; 6889; 7056; 7225;
 7396; 7569; 7744; 7921; 8100; 8281; 8464; 8649; 8836; 9025; 9216; 9409; 9604;
 9801; 10000]
---------------------------------
[15; 30; 45; 60; 75; 90]
---------------------------------
[(1, 1); (1, 2); (1, 3); (1, 4); (1, 5); (1, 6); (1, 7); (1, 8); (1, 9); (1, 10);
 (2, 1); (2, 2); (2, 3); (2, 4); (2, 5); (2, 6); (2, 7); (2, 8); (2, 9); (2, 10);
 (3, 1); (3, 2); (3, 3); (3, 4); (3, 5); (3, 6); (3, 7); (3, 8); (3, 9); (3, 10)]

在本文中,我们学习了如何在 F# 中使用列表。

作者

我叫 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已撰写超过 1400 篇文章和 8 本电子书。我在教学编程方面拥有超过十年的经验。