Erlang程序设计笔记---(第五节 Erlang的记录与映射组)

导读

到目前为止,我们已经讨论了两种数据容器,分别是元组和列表。元组用于保存固定数量的 元素,而列表用于保存可变数量的元素。

本章将介绍记录(record)和映射组(map)。记录其实就是元组的另一种形式。通过使用记 录,可以给元组里的各个元素关联一个名称。

映射组是键-值对的关联性集合。键可以是任意的Erlang数据类型。它们在Perl和Ruby里被称 为散列(hash),在C++和Java里被称为映射(map),在Lua里被称为表(table),在Python里则被 称为字典(dictionary)。

使用记录和映射组能让编程更容易。与其记住某个数据项在复杂数据结构里的存放位置,不 如使用该项的名称,让系统找到数据存放的位置。记录使用一组固定且预定义的名称,而映射组 可以动态添加新的名称。

一. 何时使用映射组或记录

记录其实就是元组的另一种形式,因此它们的存储与性能特性和元组一样。映射组比元组占 用更多的存储空间,查找起来也更慢。而另一方面,映射组比元组要灵活得多。

应该在下列情形里使用记录:

  • 当你可以用一些预先确定且数量固定的原子来表示数据时;

  • 当记录里的元素数量和元素名称不会随时间而改变时;

  • 当存储空间是个问题时,典型的案例是你有一大堆元组,并且每个元组都有相同的结构。 映射组适合以下的情形:

  • 当键不能预先知道时用来表示键-值数据结构;

  • 当方便使用很重要而效率无关紧要时作为万能的数据结构使用;

  • 用作“自解释型”的数据结构,也就是说,用户容易从键名猜出值的含义;

  • 用来表示键-值解析树,例如XML或配置文件;

  • 用JSON来和其他编程语言通信。

二. 通过记录命名元组里的项

对于小型元组而言,记住各个元素代表什么几乎不成问题,但当元组包含大量元素时,给各 个元素命名就更方便了。一旦命名了这些元素,就可以通过名称来指向它们,而不必记住它们在 元组里的具体位置。

用记录声明来命名元组里的元素,它的语法如下:

-record(Name, {
               %% 以下两个键带有默认值
               key1 = Default1,
               key2 = Default2,
               ...
               %% 下一行就相当于Key 3 = undefined
               key3,
               ...
              }).
%%% 警告:record不是一个shell命令(在shell里要用rr,详见本节后面的描述)。记录声明只能在 Erlang源代码模块里使用,不能用于shell

在之前的例子里,Name是记录名。key1、key2这些是记录所含各个字段的名称,它们必须 是原子。记录里的每个字段都可以带一个默认值,如果创建记录时没有指定某个字段的值,就会 使用默认值。

举个例子,假设想要操作一个待办事项列表。我们会首先定义一个todo记录,然后将它保 存在一个文件里(记录的定义既可以保存在Erlang源代码文件里,也可以由扩展名为.hrl的文件 保存,然后包含在Erlang源代码文件里)。

请注意,文件包含是唯一能确保多个Erlang模块共享相同记录定义的方式。它类似于C语言 用.h文件保存公共定义,然后包含在源代码文件里。

% records.hrl
-record(todo, {status=reminder,who=joe,text}).
​
% 记录一旦被定义,就可以创建该记录的实例了。要在shell里这么做,必须先把记录的定义读入shell,然后才能创建记录。我们将用shell函数 rr(read records的缩写,即读取记录)来实现。
rr("records.hrl").  % [todo]

  1. 创建和更新记录

    > rr("records.hrl")
    ​
    > #todo{}. % #todo{status = reminder,who = joe,text = undefined}
    ​
    > X2 = #todo{status=done}.  % #todo{status = done,who = joe,text = undefined}
    ​
    > X1 = #todo{status=urgent, text="Fix errata in book"}.
    ​
    %%% 我们在第2行和第3行创建了新的记录。语法#todo{key1=Val1, ..., keyN=ValN}用于创 建一个类型为todo的新纪录。所有的键都是原子,而且必须与记录定义里所用的一致。如果省略 了一个键,系统就会用记录定义里的值作为该键的默认值。在第4行复制了一个现有的记录。语法X1#todo{status=done}的意思是创建一个X1的副本 (类型必须是todo),并修改字段status的值为done。请记住,这么做生成的是原始记录的一个副本,原始记录没有变化。

  2. 提取记录字段

    % 要在一次操作中提取记录的多个字段,可以使用模式匹配
    > #todo{who=W, text=Txt} = X1.
    % #todo{status = aa,who = joe,text = undefined}
    > X1#todo.text.  % undefined

  3. 在函数里模式匹配记录

我们可以编写模式匹配记录字段或者创建新记录的函数,代码如下所示

clear_status(#todo{status=S, who=W} = R) -> 
  %% 在此函数内部,S和W绑定了记录里的字段值
  %% values in the record
  %%
  %% R是*整个*记录
  R#todo{status=finished}
  %% ...
  % 要匹配某个类型的记录,可以这样编写函数定义
  do_something(X) when is_record(X, todo) -> 
     %% ...
    这个子句会在X是todo类型的记录时匹配成功。

  1. 记录是元组的另一种形式

    > X1.
    % #todo{status = aa,who = joe,text = undefined}
    % 现在我们要让shell忘掉todo的定义
    10> rf(todo).
    > X1.
    % {todo,aa,joe,undefined}
    ​
    %%% 在第10行里,rf(todo)命令使shell忘了todo记录的定义。因此,现在打印X2时,shell将X2 显示成一个元组。其实它们在系统内部都是元组,但记录提供了方便的语法,让你可以用名称而 非位置来指明不同的元素。

三. 映射组:关联式键-值存储

映射组从Erlang的R17版开始可供使用。 映射组具有下列属性。

  • 映射组的语法与记录相似,不同之处是省略了记录名,并且键值分隔符是=>或:=。

  • 映射组是键-值对的关联性集合。

  • 映射组里的键可以是任何全绑定的Erlang数据类型(即数据结构里没有任何未绑定变量)。

  • 映射组里的各个元素根据键进行排序。

  • 在不改变键的情况下更新映射组是一种节省空间的操作。

  • 查询映射组里某个键的值是一种高效的操作。

  • 映射组有着明确的顺序。 我们将在下面几节里更详细地介绍映射组。

  1. 映射组语法

    % 映射组的写法依照以下语法:
    #{Key1 Op Val1, Key2 Op Val2, ..., KeyN Op ValN}.
    % 它的语法与记录相似,但是散列符号(即#)之后没有记录名,而Op是=>或者:=这两个符号的其中一个。
    % 键和值可以是任何有效的Erlang数据类型。
    % 举个例子,假设要创建一个包含a、b两个键的映射组。
    > F1 = #{a => 1, b=> 2}.
    ​
    % 创建一个带有非原子键的映射组
    Facts = #{{wife,fred} => "Sue",
             {age,fred} => 45,
             {daughter,fred} => "Mary",
             {likes, jim} => [...]}.
    ​
    % 映射组在系统内部是作为有序集合存储的,打印时总是使用 各键 排序后的顺序,与映射组的 创建方式无关。这里有一个例子:
    F2 = #{ b => 2, a=>1 }.  % #{a => 1,b => 2}
    F1 = F2.  % #{a => 1,b => 2}
    ​
    %要基于现有的映射组更新一个映射组,我们会使用如下语法,其中的Op(更新操作符)是 =>或:=
    NewMap = OldMap # { K1 Op V1,...,Kn Op Vn }
    % 表达式K => V有两种用途,一种是将现有键K的值更新为新值V,另一种是给映射组添加一 个全新的K-V对。这个操作总是成功的。
    % 表达式K := V的作用是将现有键K的值更新为新值V。如果被更新的映射组不包含键K,这个 操作就会失败。
    F1.  %#{a => 1,b => 2}
    F3 = F1#{ c => 3 }. % #{a => 1,b => 2,c => 3}
    F4 = F1#{ c := 3 }.  % exception error: bad key: c
    ​
    %%%
    %%使用:=操作符有两个重要原因。首先,如果拼错了新键的名称,我们希望会有错误发生。 如果创建了一个映射组Var = #{keypos => 1, ...},然后用Var #{key_pos := 2 }更新它, 那么几乎可以肯定拼错了键名,而我们需要知道这一点。第二个原因和效率有关。如果在映射组 更新操作里只使用:=操作符,那么我们就知道新旧映射组都带有一组相同的键,因此可以共享相 同的键描述符。假如我们有一个包含数百万映射组的列表,并且它们的各个键都相同,那么所节 省的空间是很可观的。
    ​
    %%%  使用映射组的最佳方式是在首次定义某个键时总是使用Key => Val,而在修改具体某个键的值时都使用Key := Val。
    %%%

  2. 模式匹配映射组字段

    用来编写映射组的=>语法还可以作为映射组模式使用。和之前一样,映射组模式里的键不 能包含任何未绑定变量,但是值现在可以包含未绑定变量了(在模式匹配成功后绑定)。

    举例:

    Henry8 = #{class => king,born => 1491, died => 1547}.
    #{born => B} = Henry8.
    B.
    #{D => 1547}.

  3. 操作映射组的内置函数

  4. 映射组排序

  5. 以JSON为桥梁

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值