7-键值列表-图-字典

    到目前还没有讲到任何关联性数据结构,即那种可以将一个或几个值关联到一个key上。
    不同语言有不同的叫法,如字典,哈希,关联数组,图,等等。

    Elixir中有两种主要的关联性结构:键值列表(keyword list)和图(map)。

    在很多函数式语言中,常用二元元组的列表来表示关联性数据结构。在Elixir中也是这样。
    当我们有了一个元组(不一定仅有两个元素的元组)的列表,并且每个元组的第一个元素是个 原子
    那就称之为键值列表:

    如你所见,Elixir使用比较特殊的语法来定义这样的列表,但实际上它们会映射到一个元组列表。
    因为它们是简单的列表而已,所有针对列表的操作,键值列表也可以用。

    比如,可以用运算符为列表添加元素:

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

    上面例子中重复出现了:a这个key,这是允许的。
    以这个key取值时,取回来的是第一个找到的(因为有序):

    1. iex> new_list = [a: 0] ++ list
    2. [a: 0, a: 1, b: 2]
    3. iex> new_list[:a]
    4. 0

    键值列表十分重要,它有两大特点:

    • 有序
    • key可以重复(!仔细看上面两个示例)

    例如,使用这两个特点
    写出了精巧的DSL(用来写数据库query):

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

    do: 和else: 就是键值列表!事实上代码等同于:

    1. :that

    当键值列表是函数最后一个参数时,方括号就成了可选的。

    为了操作关键字列表,Elixir提供了
    键值(keyword)模块
    记住,键值列表就是简单的列表,和列表一样提供了线性的性能。
    列表越长,获取长度或找到一个键值的速度越慢。
    因此,关键字列表在Elixir中一般就作为函数调用的可选项。
    如果你要存储大量数据,并且保证一个键只对应最多一个值,那就使用图。

    对键值列表做模式匹配:

    1. [a: 1]
    2. iex> a
    3. 1
    4. iex> [a: a] = [a: 1, b: 2]
    5. ** (MatchError) no match of right hand side value: [a: 1, b: 2]
    6. iex> [b: b, a: a] = [a: 1, b: 2]
    7. ** (MatchError) no match of right hand side value: [a: 1, b: 2]

    尽管如此,对列表使用模式匹配很少用到。因为不但要元素个数相等,顺序还要匹配。

    无论何时想用键-值结构,图都应该是你的第一选择。Elixir中,用%{}定义图:

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

    和键值列表对比,图有两主要区别:

    • 图允许任何类型值作为键
    • 图的键没有顺序

    如果你向图添加一个已有的键,将会覆盖之前的键-值对:

    如果图中的键都是原子,那么你也可以用键值列表中的一些语法:

    1. %{a: 1, b: 2}
    1. %{:a => 1, 2 => :b}
    2. iex> %{:a => a} = %{:a => 1, 2 => :b}
    3. %{:a => 1, 2 => :b}
    4. iex> a
    5. 1
    6. iex> %{:c => c} = %{:a => 1, 2 => :b}
    7. ** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}

    如上所示,图A与另一个图B做匹配。
    图B中只要包含有图A的键,那么两个图就能匹配上。若图A是个空的,那么任意图B都能匹配上。
    但是如果图B里不包含图A的键,那就匹配失败了。

    图还有个有趣的功能:它提供了特殊的语法来修改和访问原子键:

    1. iex> map = %{:a => 1, 2 => :b}
    2. %{:a => 1, 2 => :b}
    3. iex> map.a
    4. 1
    5. iex> %{map | :a => 2}
    6. %{:a => 2, 2 => :b}
    7. iex> %{map | :c => 3}
    8. ** (ArgumentError) argument error

    使用上面两种语法要求的前提是所给的键是切实存在的。最后一条语句错误的原因就是键:c不存在。

    未来几章中我们还将讨论结构体(structs)。结构体提供了编译时的保证,它是Elixir多态的基础。
    结构体是基于图的,上面例子提到的修改键值的前提就变得十分重要。

    提供了许多关于图的操作。
    它提供了与键值列表许多相似的API,因为这两个数据结构都实现了字典的行为。

    图是最近连同EEP 43被引入Erlang虚拟机的。
    Erlang 17提供了EEP的部分实现,只支持一小部分图功能。
    这意味着图仅在存储不多的键时,图的性能还行。
    为了解决这个问题,Elixir还提供了

    该模块提供了一个字典来支持大量的键,并且性能不错。

    Elixir中,键值列表和图都被称作字典。
    换句话说,一个字典就像一个接口(在Elixir中称之为行为behaviour)。
    键值列表和图模块实现了该接口。

    这个接口定义于Dict模块
    该模块还提供了底层实现的一个API:

    字典模块允许开发者实现自己的字典形式,提供一些特殊的功能。
    字典模块还提供了所有字典类型都可以使用的函数。
    如,可以比较两个字典类型(可以是不同的实现)。

    如果你的代码期望接受一个关键字作为参数,那么使用简直列表模块。
    如果你想操作一个图,那就使用图模块。
    如果你想你的API对所有字典类型的实现都有用,
    那就使用字典模块(确保以不同的实现作为参数测试一下)。