Erlang中的映射组Map详细介绍

    主要是遇到 Map匹配的问题,所以顺便回忆一下 Erlang 中的映射组 Map,在其它语言中被称作 Hash 哈希或者 Dict 字典。

    Erlang 从 R17 版本开始支持映射组

    创建映射组

    Erlang 中的映射组用结构 #{} 表示,创建一个映射组可以这样

    复制代码 代码如下:
    % 不管你怎么排序,最终结果都是按键的字典顺序排列的
    #{ name => “wittyfox”, age => 19 }.
    % => #{age => 20,name => “wittyfox”}

    % 也可以创建一个空的映射组
    #{}.
    % => #{}

    更新映射组

    映射组可以更新,所谓的更新是创建一个新的映射组,因为 Erlang 中的变量是不可改变的。

    复制代码 代码如下:
    % 现在的我
    Me = #{ name => “wittyfox”, age => 19 }.
    % => #{age => 19,name => “wittyfox”}

    % 过年啦,又长一岁了,变成崭新的我啦
    NewMe = Me#{ age => 20 }.
    % => #{age => 20,name => “wittyfox”}

    % 当然也可以直接修改
    #{ name => “wittyfox”, age => 19 }#{ age => 20 }.
    % => #{age => 20,name => “wittyfox”}

    => 用于创建或更新一个映射,如果键存在,则更新它,否则就创建一个新的映射。如果一不小心某个键拼写错误,Oops.

    复制代码 代码如下:
    % 本来想更新 age,结果一不小心拼写错误,创建了一个新的映射
    Me#{ aeg => 20 }.
    % => #{aeg => 20,age => 19,name => “wittyfox”}.

    为了避免这种情况,还有一种更新映射的方法,使用 :=,它只能用来更新映射,而不能创建新的映射,如果键不存在,就会抛出一个 badarg 异常。

    复制代码 代码如下:
    % 不存在 aeg 键,抛出 badarg 异常
    Me#{ aeg := 20 }.
    % ** exception error: bad argument … blabla

    % 只能更新已存在的映射
    Me#{ age := 20 }.
    % => #{age => 20,name => “wittyfox”}

    两种操作符的区别

    1.=> 可以用来更新映射或者创建新的映射
    2.:= 只能更新映射,在键不存在时会抛出异常
    所以有下面的总结

    创建映射组时

    只能使用 =>,:= 只能更新映射而无法创建新的映射,而创建映射组时需要创建若干映射

    复制代码 代码如下:
    #{ name := “wittyfox”, age := 19 }.
    % * 1: only association operators ‘=>’ are allowed in map construction

    映射组匹配的

    左边只能使用 :=,=> 在键不存在时可以创建新的映射,而映射组匹配可以部分匹配 (只匹配左边拥有的部分) ,所以匹配是没有意义的

    复制代码 代码如下:
    % 部分匹配: 我们只想取出 age,所以我们只关心参数中有没有 age 这个映射
    #{ age := Age } = Me.
    % => #{age => 19,name => “wittyfox”}

    % Age.
    % => 19

    % 不合法的匹配
    #{ age => Age } = Me.
    % * 1: illegal pattern

    为了更好的发现错误

    只在创建映射组或明确需要创建新的映射时使用 =>,而在其它场合均使用 :=

    复制代码 代码如下:
     % 这里是创建映射组,只能使用 =>
     new() ->
         {ok, {?MODULE, #{name => “wittyfox”, age => 19}}}.

     % 这里是匹配,只能使用 :=
     show({?MODULE, #{name := Name, age := Age}}) ->
         io:format(“Name: ~p, Age: ~p~n”, [Name, Age]).

    注意

    上面的更新映射,创建新的映射以及匹配可以同时针对多个映射,这里只是作为例子而只选择一对映射。

    映射组操作

    Erlang 中的 maps 模块用于操作映射组

    映射组的创建及属性

    复制代码 代码如下:
    % 创建映射组
    maps:new().
    % => #{}

    % 返回所有键
    maps:keys(Me).
    % => [age,name]

    % 判断是否存在键
    maps:is_key(age, Me).
    % => true
    maps:is_key(aeg, Me).
    % => false

    % 按键的顺序返回所有值
    maps:values(Me).
    % =>[19,”wittyfox”]

    % 映射数量
    maps:size(Me).
    % => 2

    % 还可以使用 erlang:map_size/1
    % 此函数可以用于 Guard,maps 模块内部也是使用此函数的
    map_size(Me).
    % => 2

    映射的增加、删除、获取

    复制代码 代码如下:
    % maps:get/2 在键不存在时会抛出异常
    maps:get(age, Me).
    % => 19

    % maps:get/3 在键不存在时会返回第三个参数的值
    maps:get(aeg, Me, 20).
    % => 20

    % 用于更新或创建映射,类似于 =>
    % 所谓更新,只是返回更新后的新的映射组,原映射组并不会改变
    maps:put(gender, male, Me).
    % => #{age => 19,gender => male,name => “wittyfox”}

    % 用于更新映射,类似于 :=,键不存在时会抛出 badarg 异常
    maps:update(age, 20, Me).
    % => #{age => 20,name => “wittyfox”}

    % 删除一个映射,键不存在时相当于什么都没做,不会抛出异常
    maps:remove(age, Me).
    % => #{name => “wittyfox”}

    % 查找键的值,键不存在时返回 error
    maps:find(age, Me).
    % => {ok, 19}

    maps:find(aeg, Me).
    % => error

    映射组的归并

    复制代码 代码如下:
    % 归并两个映射组,注意第二个参数是创建新的映射组,所以只能用 =>
    maps:merge(Me, #{ age => 10 }).  
    % => #{age => 10,name => “wittyfox”}

    % 相当于
    Me#{ age => 10 }.

    映射组与列表之间的转换

    复制代码 代码如下:
    % 返回映射元组对的列表
    maps:to_list(Me).
    % => [{age,19},{name,”wittyfox”}]

    % 从列表构建映射组
    maps:from_list([]).
    % => #{}

    maps:from_list([{name, “wittyfox”}, {age, 19}]).
    % => #{age => 19,name => “wittyfox”}

    映射组的遍历

    复制代码 代码如下:
    % 对映射组的每对映射执行操作
    % X, Y 分别为一对映射的键和值
    maps:map(fun (X, Y) -> io:format(“~p => ~p~n”, [X, Y]) end, Me). 
    % age => 19                                             % 输出
    % name => “wittyfox”                                    % 输出
    % => #{age => ok,name => ok}                            % 返回值

    % X, Y 分别为一对映射的键和值,V 为上一次迭代的结果,0 为迭代的初始值
    % 这里简单的用于每次迭代时值加 1,结果就是映射组的映射数量
    maps:fold(fun (X, Y, V) -> V + 1 end, 0, Me).
    % => 2

    映射组中映射的选取

    返回第一个参数中指定的键的映射组成的映射组

    复制代码 代码如下:
    maps:with([], Me).
    % => #{}

    maps:with([age], Me).
    % => #{age => 19}

    % 键可以不存在
    maps:with([aeg], Me).
    % => #{}

    返回键不再第一个参数的列表中的映射组成的映射组

    复制代码 代码如下:
    maps:without([], Me).
    % => #{age => 19,name => “wittyfox”}

    maps:without([age], Me).
    % => #{name => “wittyfox”}

    % 键也可以不存在
    maps:without([age, neme], Me).
    % => #{name => “wittyfox”}

    注意

    值得一提的是 maps 模块中的若干函数,比如 map, fold, with 和 without 都是使用 maps:to_list/1 转到列表,然后使用 lists 模块的工具处理,然后使用 maps:from_list/1 转回到映射组的。

发表评论

发表评论