Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

使用列表存储多个值

集合数据结构通常可以包含多个值。与内置的元组和记录类型不同,这些集合指向存储在堆上的数据,这意味着数据量在编译时不必知道,并且可以随着程序的运行而增长或缩小。每种集合都有不同的功能和权衡,选择适合你当前情况的集合是你会随着时间积累的技能。在本章中,我们将讨论 X 语言标准库中使用的三个非常常见的集合:

  • 列表:允许你存储可变数量的值,一个接一个。
  • Map:允许你将一个值(键)与另一个值(值)相关联。
  • Set:允许你存储唯一值的集合。

在本章中,我们将介绍列表。我们将在接下来的章节中介绍 Map 和 Set。

什么是列表?

列表是一个值的集合,所有值都具有相同的类型。X 语言使用 List<T> 表示列表类型,其中 T 是元素的类型。列表类型也有一个简写语法 [T]

列表在许多编程语言中也被称为数组或向量。在 X 语言中,List 是标准库提供的集合类型。

创建列表

创建列表有几种方法。让我们从最简单的方法开始:使用方括号语法列出值:

let v = [1, 2, 3, 4, 5]

这将创建一个包含五个整数的新列表。列表的类型从它包含的值中推断出来;在这种情况下,v 的类型是 List<integer>

我们也可以显式注解类型:

let v: [integer] = [1, 2, 3, 4, 5]

或者使用完整的 List<T> 语法:

let v: List<integer> = [1, 2, 3, 4, 5]

创建空列表

我们也可以创建一个空列表,然后向其中添加元素。为此,我们通常需要注解类型,因为编译器无法从空列表中推断出元素类型:

let v: List<integer> = []

使用 List::new

另一种创建空列表的方法是使用 List::new 函数:

let v: List<integer> = List::new()

读取列表元素

现在我们知道如何创建列表,让我们谈谈如何访问它们的元素。有两种方法可以引用列表中存储的值:通过索引或使用 get 方法。

索引访问

我们可以使用方括号和索引直接访问列表元素:

let v = [1, 2, 3, 4, 5]
let third: integer = v[2]
println("第三个元素是 ", third)

注意:与大多数编程语言一样,列表使用从零开始的索引,所以第一个元素在索引 0 处。

使用 get 方法

访问元素的另一种方法是使用 get 方法,它返回一个 Option<T>

let v = [1, 2, 3, 4, 5]
let third: Option<integer> = v.get(2)
when third is {
  Some(x) => println("第三个元素是 ", x),
  None => println("没有第三个元素。")
}

当我们使用 get 方法时,我们得到一个 Option<T>,如果索引超出范围,它将是 None,而不是导致程序崩溃。当你尝试访问超过列表末尾的元素时,你应该使用哪种方法?这取决于你!让我们看看当我们有一个包含五个元素的列表,然后尝试访问索引为 100 的元素时会发生什么:

let v = [1, 2, 3, 4, 5]
// let does_not_exist = v[100]  // 这会导致错误!
let does_not_exist = v.get(100)  // 这会返回 None

当索引超出范围时,第一种方法 [] 会导致错误(我们称之为 panic)。第二种方法 get 只会返回 None,而不会导致程序崩溃。你应该选择哪种方法取决于你认为尝试访问超出列表末尾的元素是正常的、偶尔发生的情况,还是应该被视为错误的情况,在这种情况下,你希望程序停止并显示错误。

遍历列表中的元素

如果我们想依次访问列表中的每个元素,我们可以遍历所有元素,而不是使用索引一次访问一个元素。清单显示了如何使用 for 循环来获取列表中每个元素的不可变引用并打印它们。

let v = [100, 32, 57]
for i in v {
  println(i)
}

我们还可以遍历可变列表中元素的可变引用,以便对所有元素进行更改:

let mutable v = [100, 32, 57]
for i in &mut v {
  i = i + 50
  println(i)
}

为了更改可变引用指向的值,我们需要在为其赋值之前解引用 i。我们将在第 13 章更详细地讨论解引用运算符。现在,你只需要知道 i 是对列表中每个元素的可变引用,并且你需要使用 = 来更改该引用指向的值。

常见的 List 操作

List 类型有许多有用的方法。让我们看看其中一些最常见的。

获取列表长度

我们可以使用 len 方法获取列表的长度:

let v = [1, 2, 3, 4, 5]
println("列表长度: ", v.len())  // 打印 5

检查列表是否为空

我们可以使用 is_empty 方法检查列表是否为空:

let v: List<integer> = []
println("列表为空: ", v.is_empty())  // 打印 true

向列表添加元素

虽然列表是不可变的,但我们可以通过创建包含旧元素和新元素的新列表来有效地向它们添加元素。让我们看看如何使用标准库中的方法来做到这一点:

let v = [1, 2, 3]
let v2 = List::push(v, 4)  // [1, 2, 3, 4]
let v3 = List::prepend(v2, 0)  // [0, 1, 2, 3, 4]

注意:这些方法返回新列表,而不是修改原始列表。这符合 X 语言对不可变性的偏好。

连接列表

我们可以使用 concat 方法连接两个列表:

let v1 = [1, 2, 3]
let v2 = [4, 5, 6]
let v3 = List::concat(v1, v2)  // [1, 2, 3, 4, 5, 6]

映射列表

我们可以使用 map 方法对列表中的每个元素应用函数:

let v = [1, 2, 3]
let v2 = List::map(v, function(x) { x * 2 })  // [2, 4, 6]

过滤列表

我们可以使用 filter 方法只保留满足条件的元素:

let v = [1, 2, 3, 4, 5, 6]
let v2 = List::filter(v, function(x) { x % 2 == 0 })  // [2, 4, 6]

使用枚举存储多种类型

列表只能存储相同类型的值。这可能不方便;当然有需要存储不同类型项目列表的用例。幸运的是,枚举的变体是在同一枚举类型下定义的,所以当我们需要一种类型来表示不同类型的元素时,我们可以定义并使用枚举!

例如,假设我们想从电子表格的一行中获取值,该行中的某些列包含整数,一些包含浮点数,一些包含字符串。我们可以定义一个枚举,其变体将容纳不同类型的值,然后所有枚举变体将被视为同一类型:枚举的类型。然后我们可以创建一个该枚举类型的列表,以最终容纳不同的类型。我们在清单中演示了这一点。

type SpreadsheetCell = Integer(integer) | Float(float) | Text(string)

let row = [
  Integer(3),
  Float(10.12),
  Text(String::from("你好"))
]

通过将枚举与列表一起使用,我们明确表示只允许哪些值进入我们的列表。此外,X 语言在编译时确切知道列表中存在哪些类型,因此我们可以安全地使用 when/is 模式匹配来处理列表中的每个元素。

for cell in row {
  when cell is {
    Integer(i) => println("整数: ", i),
    Float(f) => println("浮点数: ", f),
    Text(t) => println("文本: ", t)
  }
}

如果你在编写程序时不知道程序运行时列表将需要存储的完整类型集,枚举技术将不起作用。相反,你可以使用 trait 对象,我们将在第 8 章介绍。

现在我们已经讨论了列表,让我们继续讨论 Map!