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

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

相关推荐
海绵波波1073 小时前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
B1nna4 小时前
Redis学习(三)缓存
redis·学习·缓存
_im.m.z4 小时前
【设计模式学习笔记】1. 设计模式概述
笔记·学习·设计模式
网络风云5 小时前
【魅力golang】之-反射
开发语言·后端·golang
Q_19284999065 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
胡西风_foxww6 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator
运维&陈同学6 小时前
【Kibana01】企业级日志分析系统ELK之Kibana的安装与介绍
运维·后端·elk·elasticsearch·云原生·自动化·kibana·日志收集
左漫在成长7 小时前
王佩丰24节Excel学习笔记——第十九讲:Indirect函数
笔记·学习·excel
纪伊路上盛名在7 小时前
Max AI prompt1
笔记·学习·学习方法
Suwg2098 小时前
【MySQL】踩坑笔记——保存带有换行符等特殊字符的数据,需要进行转义保存
数据库·笔记·mysql