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.使用映射来处理具有预定义键集的数据

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

相关推荐
李小星同志12 分钟前
高级算法设计与分析 学习笔记6 B树
笔记·学习
霜晨月c23 分钟前
MFC 使用细节
笔记·学习·mfc
Jhxbdks36 分钟前
C语言中的一些小知识(二)
c语言·开发语言·笔记
小江湖199437 分钟前
元数据保护者,Caesium压缩不丢重要信息
运维·学习·软件需求·改行学it
AlexMercer10121 小时前
【C++】二、数据类型 (同C)
c语言·开发语言·数据结构·c++·笔记·算法
Adolf_19931 小时前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
海里真的有鱼1 小时前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺1 小时前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis