Elixir 编程Elixir & PhoenixElixir

Elixir 简明笔记(十四) --- 字典容器

2016-04-07  本文已影响609人  人世间

Elixir提供了丰富的数据类型(实际上只是基本类型的扩展)。前面介绍了元组和列表。他们的特点就是线性结构,通常称之为序列。此外,elixir还有一些哈希类型的结构,这些可以称之为容器集合(collection)结构。下面就来介绍elixir中的一些哈希容器。

图 map

哈希结构是通过一些键值对组合的字典。每一种编程语言中都提供了哈希结构。elixir中更是提供了多种哈希结构。其中Map就是比较常用的一种。图是由一个键(key)和值(value)为基本存储单元的结构。key和value都可以是任意类型的数据。Map是在Erlang/OTP 17.0之后引入的数据结构。map通常只用来表示元素比较少的情况,当有大量元素的时候,elixir推荐使用HashDict这个结构(稍后介绍)。

map 的定义

map的定义也比较简单,使用一个%跟随一堆花括号{}。键和值之间使用 =>(通常其他hash结构使用:, 例如python和js)。例如:

iex(1)> bob = %{:name => "Bob", :age => 25, :work_at => "Initech"}
%{age: 25, name: "Bob", work_at: "Initech"}

定义了一个简单地map,其中键都是Atom类型,值有字符串和数字。针对这种map,可以使用下面的语法糖简写:

iex(1)> bob = %{:name => "Bob", :age => 25, :work_at => "Initech"}
%{age: 25, name: "Bob", work_at: "Initech"}
iex(2)> bob2 = %{name: "Bob", age: 25, work_at: "Initech"}
%{age: 25, name: "Bob", work_at: "Initech"}
iex(3)> bob === bob2
true


#### map 的读取

读取一个map键的值,使用中括号[]加上键名的atom即可。也可以使用 .操作符,前者访问不存在的键的时候,将会返回nil,后者则会抛出语法错误:

iex(5)> bob[:work_at]
"Initech"
iex(6)> bob[:non_existent_field]
nil
iex(7)> bob.work_at
"Initech"
iex(8)> bob.:non_existent_field
** (SyntaxError) iex:8: syntax error before: non_existent_field

map 的修改

map的修改使用 | ,因为elixir的数据都是不可变的,因此修改将会返回一个新的map。也可以同时修改多个键的值。但是只能修改已经存在的key,否则会报错。之所以这样限制,其目的是为了更有效的提高map更新的效率。因为map结构不会变,那么修改前后各个“版本”的map还将会被引用,可是使用更少的内存,操作也就更加迅速:

iex(8)> next_years_bob = %{bob | age: 26}
%{age: 26, name: "Bob", work_at: "Initech"}
iex(9)> bob
%{age: 25, name: "Bob", work_at: "Initech"}
iex(10)> %{bob | age: 26, work_at: "Initrode"}
%{age: 26, name: "Bob", work_at: "Initrode"}
iex(11)> %{bob | non_existent_feild: 'new'}
** (ArgumentError) argument error
    (stdlib) :maps.update(:non_existent_feild, 'new', %{age: 25, name: "Bob", work_at: "Initech"})
    (stdlib) erl_eval.erl:255: anonymous fn/2 in :erl_eval.expr/5
    (stdlib) lists.erl:1261: :lists.foldl/3

除了使用 | 更新map之外,还可以使用模块的方法,即使用Map模块和Dict模块来更新map。并且使用模块的方式可以增加新的key和值, 但是不能同时更新多个值:

iex(13)> Map.put(bob, :salary, 5000)
%{age: 25, name: "Bob", salary: 5000, work_at: "Initech"}
iex(14)> Map.put(bob, :age, 26)
%{age: 26, name: "Bob", work_at: "Initech"}
iex(15)> Map.put(bob, :age, 26, :work_at, "Home")
** (UndefinedFunctionError) undefined function: Map.put/5
    (elixir) Map.put(%{age: 25, name: "Bob", work_at: "Initech"}, :age, 26, :work_at, "Home")

iex(15)> Dict.put(bob, :salary, 5000)
%{age: 25, name: "Bob", salary: 5000, work_at: "Initech"}
iex(16)> Dict.put(bob, :age, 26)
%{age: 26, name: "Bob", work_at: "Initech"}
iex(17)> Dict.put(bob, :salary, 5000, :work_at, "Home")
** (UndefinedFunctionError) undefined function: Dict.put/5
    (elixir) Dict.put(%{age: 25, name: "Bob", work_at: "Initech"}, :salary, 5000, :work_at, "Home")

通常情况下,使用Map模块要比Dict模块的操作速度更快,不过map用于存储少量的元素。因此,其自身提供的方法可以应付很多应用场景。

map的模式匹配

毫无疑问,elixir中模式匹配无处不在,map当然也可以进行模式匹配。与列表,元组不一样,map的模式匹配,不需要把所有的key和value都写入模式里, 匹配不存在的key会失败,因而会报错:

iex(17)> %{name: real_name} = bob
%{age: 25, name: "Bob", work_at: "Initech"}
iex(18)> real_name
"Bob"
iex(19)> %{name: real_name, age: real_age} = bob
%{age: 25, name: "Bob", work_at: "Initech"}
iex(20)> %{name: real_name, age: real_age, salary: real_salary} = bob
** (MatchError) no match of right hand side value: %{age: 25, name: "Bob", work_at: "Initech"}

关键字列表 Keyword List

定义

map是标准的哈稀结构,这样的键值字典,elixir还有好几种。Keyword算是一种奇怪的哈稀,有其行而无其实。通常定义一个列表使用中括号。定义一个两个元素的元组。如果元祖的第一个元素是一个atom,那么就可以写成下面类似哈稀的结构:

iex(20)> bob = [{:name, "Bob"}, {:age, 25}, {:work_at, "Intiech"}]
[name: "Bob", age: 25, work_at: "Intiech"]                         # 返回值就是keyword list的字面方式
iex(21)> bob2 = [name: "Bob", age: 25, work_at: Intiech]
[name: "Bob", age: 25, work_at: "Intiech"]
iex(22)> bob === bob2
true
iex(25)> tom = [{25, :age}, {:name, "Tom"}]
[{25, :age}, {:name, "Tom"}]

获取keyword的值

keyword list的读取可以使用 Keyword 模块。通常keyword也是比较小的key-value数据结构:

iex(28)> Keyword.get(days, :monday)
1
iex(29)> Keyword.get(days, :noday)
nil
iex(31)> days[:monday]
1
iex(32)> days[:noday]
nil
iex(33)> days.monday
** (ArgumentError) argument error
    :erlang.apply([monday: 1, tuesday: 2, wednesday: 3], :monday, [])

keyword 的读取操作和map及其类似。可是keyword毕竟还是列表,读取某个键的值所消耗的时间复杂度还是O(n)。而map则是O(1)。keyword的应用场景呢?通常用于函数的参数来传递,例如Float.to_string的使用:

iex(35)> Float.to_string(1/3)
"3.33333333333333314830e-01"
iex(36)> Float.to_string(1/3, [decimals: 2])
"0.33"

实际使用中,elixir往往可以让你省略中苦熬和的书写:

iex(37)> Float.to_string(1/3, decimals: 2, compact: true)
"0.33"
iex(38)> Float.to_string(1/3, [decimals: 2, compact: true])
"0.33"

keyword 和 map如此相似,你肯定有疑问到底如何取舍。Elixir中很多函数的可选参数都是keyword,主要原因却有点滑稽。因为Map对于Erlang是比较新的数据结构,在此之前,map所能执行的功能基本都是使用keyword。当然,即使增加了map,两者也不是完全可以替代。比如,keyword允许多个相同的key存在,并且每个key-value是有顺序的。因此实际情况中,他们两者的使用更多的取决于当时你的应用场景。

HashDict

map适合元素比较少的key-value结构。当遭遇大量元素的适合,HashDict将会是很好的帮手。

创建

HashDict是一个模块,用于创建 HashDict 的“实例”结构。使用模块的new方法可以创建一个空的HashDict结构。

iex(39)> HashDict.new
#HashDict<[]>

为了创建多个元素的HashDict,需要借助Enum模块的into方法:

iex(47)> days = [monday: 1, tuesday: 2, wednesday: 3] |>
...(47)>    Enum.into(HashDict.new)
#HashDict<[monday: 1, tuesday: 2, wednesday: 3]>

Enum.into 函数可以将任何可以枚举(enumerable)转换成可以容器化(collectable)的结构。关于可枚举和可容器化将会在协议(protcol)中讨论。此时只需要制知道如何借助其创建一个HashDict。

读写HashDict

获取key的值比较简单,使用模块的get方法接口,当key不存在,会返回nil

iex(48)> HashDict.get(days, :monday)
1
iex(49)> HashDict.get(days, :nodays)
nil

使用 put 方法进行修改和增加新的key-value

iex(51)> HashDict.put(days, :thursday, 4)
#HashDict<[monday: 1, tuesday: 2, wednesday: 3, thursday: 4]>
iex(52)> HashDict.put(days, :tuesday, "two")
#HashDict<[monday: 1, tuesday: "two", wednesday: 3]>

由此可见,类似的容器字典结构,都可以使用 Map,Keyword,HashDict的get方法获取不存在的key的值,使用put方法更新和增加key-value。

HashDict实现了枚举协议,也就是可枚举,当然可以使用Enum的一些方法,比如枚举key-value

iex(54)> days = HashDict.put(days, :thursday, 4)
#HashDict<[monday: 1, tuesday: 2, wednesday: 3, thursday: 4]>
iex(55)> Enum.each(
...(55)>           days,
...(55)>           fn(key_value) ->
...(55)>             key = elem(key_value, 0)
...(55)>             value = elem(key_value, 1)
...(55)>             IO.puts "#{key} => #{value}"
...(55)> end )
monday => 1
tuesday => 2
wednesday => 3
thursday => 4
:ok

HashDict和map的差别还是比较好区分,HashDict对于大量的元素集合,其性能会比map好,而map却提供了很多操作上语法糖。

无论Map,Keyword,HashDict都是实现字典式的操作。可以发现,他们的模块方法,通常配合这Enum模块进行使用。实际上,Enum模块是Elixir中重要模块,它提供了很多对数据结构操作的高级封装。有些函数直接省去了需要递归实现的循环。

一般的编程语言的介绍,通常流程就是介绍其基本数据结构,然后就介绍一下控制结构。至此,elixir基本数据类型我们都了解了一遍,接下来将会讨论其控制结构。

上一篇下一篇

猜你喜欢

热点阅读