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

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基本数据类型我们都了解了一遍,接下来将会讨论其控制结构。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,550评论 18 139
  • //Clojure入门教程: Clojure – Functional Programming for the J...
    葡萄喃喃呓语阅读 3,607评论 0 7
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,524评论 18 399
  • 风扇呼呼的回声,鱼缸水流的响声,厨房高压锅飘散着浓郁的香味,时光剪在炎热的夏季,衣服贴肉的躁热,汗流鼻尖的黏湿。 ...
    书眠阅读 157评论 0 0
  • 情绪不对时,果然不适合看爱情片。 上段时间,有两天不在状态,在家看了两部韩国纯爱电影,感动得一败涂地,幸好及时看了...
    阿银老师阅读 1,507评论 0 4