Elixir基础笔记(四)—— 关键字列表和映射

在Elixir中我们提供了两种关联数据结构:关键字列表和映射。和其他语言中的字典,哈希,关联数组等类似。

关键字列表(Keyword Lists)

在Elixir中,当我们有一个元祖列表,并且元祖的第一个元素是原子的时候,我们称它为关键字列表。

1
2
3
4
iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true

就像上面的那样,Elixir支持特殊的语法来定义关键字列表[key: value]。由于关键字列表也是列表的一种,我们可以使用所有的列表操作符。

1
2
3
4
iex> list ++ [c: 3]
[a: 1, b: 2, c: 3]
iex> [a: 0] ++ list
[a: 0, a: 1, b: 2]

关键字列表有3个重要且独特的特征

  • 键值必须是原子
  • 键值是有顺序的
  • 键值可以出现多次

例如,Ecto库就用到了关键字列表来写数据库查询

1
2
3
4
query = from w in Weather,
where: w.prcp > 0,
where: w.temp < 20,
select: w

事实上if/2函数支持下面的语法。

1
2
iex> if false, do: :this, else: :that
:that

其中的do:else:就是一个关键字列表。实际上上面的表达式与下面的是等价的。

1
2
3
4
5
iex> if(false, [do: :this, else: :that])
:that

iex> if(false, [{:do, :this}, {:else, :that}])
:that

一般来说,当我们使用关键字列表当作函数的最后一个参数时,方括号可以省略。

虽然我们可以对关键字列表进行模式匹配,但是我们确很少这么做,因为我们需要匹配列表中的各个内容。

1
2
3
4
5
6
7
8
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还提供了Keyword模块用来操作关键字列表。由于列表变多之后会花费更多的时间来找到键值,或者获取列表的长度。所以关键字列表一般用于存储可选值。如果你有存储许多内容,并且每个关键字是唯一的,那么映射(Map)可能会更符合你的需求。

映射(Map)

映射使用%{}语法来进行创建。

1
2
3
4
5
6
7
8
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil

与关键字列表相比,我们看到它与关键字列表与2个不同点。

  • 映射允许各种类型的值作为键
  • 映射的键值没有顺序

同关键字列表相比,映射非常适合进行模式匹配。

1
2
3
4
5
6
7
8
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}

Tips: 空的映射能够匹配所有的映射

可以在匹配,访问或者添加映射键值时使用变量。

1
2
3
4
5
6
7
8
iex> n = 1
1
iex> map = %{n => :one}
%{1 => :one}
iex> map[n]
:one
iex> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
%{1 => :one, 2 => :two, 3 => :three}

Map模块同样提供了与Keyword类似的函数来操作映射

1
2
3
4
5
6
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}]

映射可以使用下面的语法来更新一个键的值,但是首先需要更改的键值存在,否则会报错。

1
2
3
4
5
6
7
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}

iex> %{map | 2 => "two"}
%{2 => "two", :a => 1}
iex> %{map | :c => 3}
** (KeyError) key :c not found in: %{2 => :b, :a => 1}

当映射中所有的键值是原子的时候可以使用关键字列表的语法

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

复杂的数据结构

通常我们的数据结构包括映射中包含映射,甚至是映射中包含关键字列表,我们可以使用put_in/2update_in/2来操作这种复杂结构。

1
2
3
4
5
6
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"}]
1
2
iex> users[:john].age
27
1
2
3
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"}]
1
2
3
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"}]