Elixir学习笔记——关键字列表和映射

现在让我们来谈谈关联数据结构。关联数据结构能够将键与某个值关联起来。不同的语言对这些结构有不同的称呼,如字典、哈希、关联数组等。

在 Elixir 中,我们有两种主要的关联数据结构:关键字列表和映射。

关键字列表

关键字列表是一种用于将选项传递给函数的数据结构。假设您想要拆分一串数字。我们可以使用 String.split/2:

iex> String.split("1 2 3", " ")

["1", "2", "3"]

但是,如果数字之间有一个额外的空格会发生什么:

iex> String.split("1 2 3", " ")

["1", "", "2", "", "3"]

如您所见,我们的结果中现在有空字符串。幸运的是,String.split/3 函数允许将 trim 选项设置为 true:

iex> String.split("1 2 3", " ", [trim: true])

["1", "2", "3"]

[trim: true] 是一个关键字列表。此外,当关键字列表是函数的最后一个参数时,我们可以跳过括号并写入:

iex> String.split("1 2 3", " ", trim: true)

["1", "2", "3"]

如上例所示,关键字列表主要用作函数的可选参数。

顾名思义,关键字列表就是列表。具体来说,它们是由 2 项元组组成的列表,其中第一个元素(键)是原子,第二个元素可以是任何值。两种表示形式相同:

iex> [{:trim, true}] == [trim: true]

true

由于关键字列表是列表,因此我们可以使用列表可用的所有操作。例如,我们可以使用 ++ 向关键字列表添加新值:

iex> list = [a: 1, b: 2]

[a: 1, b: 2]

iex> list ++ [c: 3]

[a: 1, b: 2, c: 3]

iex> [a: 0] ++ list

[a: 0, a: 1, b: 2]

您可以使用括号语法读取关键字列表的值。这也称为访问语法,因为它由 Access 模块定义:

iex> list[:a]

1

iex> list[:b]

2

如果有重复的键,则添加到前面的值是获取的值:

iex> new_list = [a: 0] ++ list

[a: 0, a: 1, b: 2]

iex> new_list[:a]

0

关键字列表很重要,因为它们具有三个特殊特征:

1.键必须是原子。

2.键是按开发人员指定的顺序排列的。

3.键可以多次使用。

例如,Ecto 库利用这些功能为编写数据库查询提供了优雅的 DSL:

query =

from w in Weather,

where: w.prcp > 0,

where: w.temp < 20,

select: w

虽然我们可以在关键字列表上进行模式匹配,但实际上并没有这样做,因为列表上的模式匹配需要匹配项目的数量及其顺序:

iex> [a: a] = [a: 1]

[a: 1]

iex> a

1

iex> [a: a] = [a: 1, b: 2]

** (MatchError) no match of right hand side value: [a: 1, b: 2]

iex> [b: b, a: a] = [a: 1, b: 2]

** (MatchError) no match of right hand side value: [a: 1, b: 2]

此外,鉴于关键字列表通常用作可选参数,它们用于并非所有键都存在的情况,这将使其无法匹配它们。简而言之,不要对关键字列表进行模式匹配。

为了操作关键字列表,Elixir 提供了关键字模块。但请记住,关键字列表只是列表,因此它们提供与它们相同的线性性能特征:列表越长,查找键、计算项目数量等所需的时间就越长。如果您需要在键值数据结构中存储大量键,Elixir 提供了映射,我们很快就会学到。

do-blocks 和关键字

正如我们所见,关键字在语言中主要用于传递可选值。事实上,我们之前在本指南中已经使用过关键字。例如,我们已经看到:

iex> if true do

... > "This will be seen"

... > else

... > "This won't"

... > end

"This will be seen"

do 块只不过是关键字之上的语法便利。我们可以将上述内容重写为:

iex> if true, do: "This will be seen", else: "This won't"

"This will be seen"

密切关注这两种语法。在关键字列表格式中,我们用逗号分隔每个键值对,每个键后面跟着 :。在 do-blocks 中,我们删除了冒号和逗号,并用换行符分隔每个关键字。它们之所以有用,正是因为它们在编写代码块时消除了冗长。大多数时候,您会使用块语法,但最好知道它们是等效的。

这在语言中起着重要作用,因为它允许 Elixir 语法保持简洁但仍然富有表现力。我们只需要几个数据结构来表示语言,这个话题我们会在讨论可选语法时回顾,并在讨论元编程时深入探讨。

说完这些,我们来谈谈映射。

映射作为键值对

每当您需要存储键值对时,映射都是 Elixir 中的"首选"数据结构。使用 %{} 语法创建映射:

iex> map = %{:a => 1, 2 => :b}

%{2 => :b, :a => 1}

iex> map[:a]

1

iex> map[2]

:b

iex> map[:c]

nil

与关键字列表相比,我们已经看到两个区别:

1.映射允许任何值作为键。

2.映射的键不遵循任何顺序。

与关键字列表相比,映射在模式匹配方面非常有用。当在模式中使用映射时,它将始终匹配给定值的子集:

iex> %{} = %{:a => 1, 2 => :b}

%{2 => :b, :a => 1}

iex> %{:a => a} = %{:a => 1, 2 => :b}

%{2 => :b, :a => 1}

iex> a

1

iex> %{:c => c} = %{:a => 1, 2 => :b}

** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}

如上所示,只要模式中的键存在于给定映射中,映射就会匹配。因此,空映射匹配所有映射。

Map 模块提供了与 Keyword 模块非常相似的 API,并提供了添加、删除和更新地图键的便捷函数:

iex> Map.get(%{:a => 1, 2 => :b}, :a)

1

iex> Map.put(%{:a => 1, 2 => :b}, :c, 3)

%{2 => :b, :a => 1, :c => 3}

iex> Map.to_list(%{:a => 1, 2 => :b})

[{2, :b}, {:a, 1}]

预定义键的映射

在上一节中,我们将映射用作键值数据结构,其中可以随时添加或删除键。但是,使用预定义键集创建映射也很常见。它们的值可能会更新,但永远不会添加或删除新键。当我们知道正在处理的数据的形状时,这很有用,如果我们得到不同的键,则很可能意味着其他地方犯了错误。

我们使用与上一节相同的语法定义此类映射,但所有键都必须是原子:

iex> map = %{:name => "John", :age => 23}

%{name: "John", age: 23}

从上面的打印结果可以看出,Elixir 还允许您使用与关键字列表相同的 key: value 语法编写原子键的映射。

当键是原子时,特别是在使用预定义键的映射时,我们也可以使用 map.key 语法访问它们:

iex> map = %{name: "John", age: 23}

%{name: "John", age: 23}

iex> map.name

"John"

iex> map.agee

** (KeyError) key :agee not found in: %{name: "John", age: 23}

还有用于更新键的语法,如果键尚未定义,也会引发此错误:

iex> %{map | name: "Mary"}

%{name: "Mary", age: 23}

iex> %{map | agee: 27}

** (KeyError) key :agee not found in: %{name: "John", age: 23}

这些操作有一个很大的好处,即如果键在映射中不存在,它们会引发此错误,并且编译器甚至可以在可能时检测并发出警告。这使得它们有助于快速获得反馈并尽早发现错误和拼写错误。这也是用于支持 Elixir 另一项功能"Structs"的语法,我们将在后面学习。

Elixir 开发人员在使用映射时通常更喜欢使用 map.key 语法和模式匹配,而不是 Map 模块中的函数,因为它们可以实现一种断言式的编程风格。José Valim 的这篇博文提供了一些见解和示例,说明如何通过在 Elixir 中编写断言式代码来获得更简洁、更快速的软件。

嵌套数据结构

我们通常会在映射内有映射,甚至在映射内有关键字列表,等等。Elixir 通过 put_in/2、update_in/2 和其他宏提供了操作嵌套数据结构的便利,提供了与命令式语言相同的便利,同时保留了语言的不可变属性。

假设您有以下结构:

iex> users = [

john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},

mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}

]

[

john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},

mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}

]

我们有一个用户关键字列表,其中每个值都是一个映射,其中包含每个用户喜欢的姓名、年龄和编程语言列表。如果我们想访问 john 的年龄,我们可以这样写:

iex> users[:john].age

27

我们也可以使用相同的语法来更新值:

iex> users = put_in users[:john].age, 31

[

john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},

mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}

]

update_in/2 宏类似,但允许我们传递一个控制值如何变化的函数。例如,让我们从 Mary 的语言列表中删除"Clojure":

iex> users = update_in users[:mary].languages, fn languages -> List.delete(languages, "Clojure") end

[

john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},

mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}

]

关于 put_in/2 和 update_in/2 还有更多需要学习的内容,包括 get_and_update_in/2,它允许我们提取值并立即更新数据结构。还有 put_in/3、update_in/3 和 get_and_update_in/3,它们允许动态访问数据结构。

摘要

Elixir 中有两种不同的数据结构可用于处理键值存储。除了 Access 模块和模式匹配之外,它们还提供了一套丰富的工具来处理复杂的、可能嵌套的数据结构。

在结束本章时,需要牢记的是您应该:

1.使用关键字列表将可选值传递给函数

2.使用映射来处理一般的键值数据结构

3.使用映射来处理具有预定义键集的数据

现在让我们来谈谈模块和函数。

相关推荐
kingwebo'sZone1 小时前
ASP.net WebAPI 上传图片实例(保存显示随机文件名)
后端·asp.net
桑榆肖物1 小时前
一个简单的ASP.NET 一致性返回工具库
后端·asp.net
一尘之中2 小时前
使用 PyTorch TunableOp 加速 ROCm 上的模型
人工智能·pytorch·学习
honey ball2 小时前
LLC与反激电路设计【学习笔记】
单片机·嵌入式硬件·学习
组态软件4 小时前
web组态软件
前端·后端·物联网·编辑器·html
Peter_chq4 小时前
【计算机网络】多路转接之select
linux·c语言·开发语言·网络·c++·后端·select
慢慢来_5 小时前
【力扣热题100】[Java版] 刷题笔记-448. 找到所有数组中消失的数字
笔记·算法·leetcode
如生命般费解的谜团5 小时前
LLM学习笔记(7)Scaled Dot-product Attention
人工智能·笔记·学习·语言模型·json
cnsxjean7 小时前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构
Mephisto.java7 小时前
【大数据学习 | Spark-Core】详解Spark的Shuffle阶段
大数据·学习·spark