关键字列表有什么好处?


101

在长生不老药中,我们有地图:

> map = %{:a => "one", :b => "two"} # = %{a: "one", b: "two"}
> map.a                             # = "one"
> map[:a]                           # = "one"

我们还有关键字列表:

> kl = [a: "one", b: "two"]       # = [a: "one", b: "two"]
> kl2 = [{:a, "one"},{:b, "two"}] # = [a: "one", b: "two"]
> kl == kl2                       # = true
> kl[:a]                          # = "one"
> kl.a                            # = ** (ArgumentError)

为什么两者都?

句法?是因为关键字列表具有更灵活的语法,允许将它们定义为没有卷曲,甚至没有括号作为函数调用的最后一个参数吗?那为什么不给Maps这种语法糖呢?

重复的密钥?是因为关键字列表可以有重复的关键字吗?为什么要同时使用地图样式和重复键?

性能?是否因为关键字列表的性能更好?那为什么要有地图呢?而且,地图不应该比键元组列表在按键查找成员方面更有表现吗?

JS Array和Ruby Hash一样的外观?是吗

我了解从结构上讲它们是不同的数据表示形式。在我看来,长生不老药中的关键字列表通过特殊的语法(3种不同的语法变体),用例与地图的重叠以及不清楚的好处,使该语言变得复杂。

使用关键字列表有什么好处?

Answers:


143
                   ┌──────────────┬────────────┬───────────────────────┐
                   │ Keyword List │ Map/Struct │ HashDict (deprecated) │
┌──────────────────┼──────────────┼────────────┼───────────────────────┤
│ Duplicate keys   │ yes          │ no         │ no                    │
│ Ordered          │ yes          │ no         │ no                    │
│ Pattern matching │ yes          │ yes        │ no                    │
│ Performance¹     │ —            │ —          │ —                     │
│ ├ Insert         │ very fast²   │ fast³      │ fast⁴                 │
│ └ Access         │ slow⁵        │ fast³      │ fast⁴                 │
└──────────────────┴──────────────┴────────────┴───────────────────────┘

关键字列表轻巧,并且在其下方具有简单的结构,因此非常灵活。您可以将它们视为Erlang约定之上的语法糖,从而可以轻松地与Erlang交互,而无需编写过于丑陋的代码。例如,关键字列表用于表示函数参数,这是从Erlang继承的属性。在某些情况下,关键字列表是您唯一的选择,尤其是在您需要重复的键或排序的情况下。它们仅具有与其他替代方案不同的属性,这使其更适合某些情况,而不适用于其他情况。

映射(和结构)用于存储实际的有效载荷数据,因为它们具有基于哈希的实现。内部的关键字列表只是每个操作都需要遍历的列表,因此它们不具有经典的键值数据结构的属性,例如恒定时间访问。

Elixir还引入HashDict了一种解决方法,以解决在编写地图时地图性能不佳的问题。但是,此问题现已从Elixir 1.0.5 / Erlang 18.0开始修复,HashDict 并将在以后的版本中弃用

如果您更深入地了解Erlang标准库,则还有更多存储键/值对的数据结构:

  • 属性列表–与Elixir关键字列表类似
  • 地图 –与Elixir地图相同
  • dict –从Erlang原语构建的键值字典
  • gb_trees –一般平衡树

当您需要跨多个进程和/或VM存储键/值对时,还可以使用以下选项:

  • ets / dets –(基于磁盘)Erlang术语存储
  • mnesia –分布式数据库

¹一般而言,但这当然取决于 ™。

²最佳情况只是在列表前。

³适用于Elixir 1.0.5及更高版本,在旧版本中可能会变慢。

HashDict现在已被弃用。

⁵需要进行线性搜索,平均搜索一半的元素。


1
允许重复的密钥并进行排序不是好处,而是不同的属性。您需要选择适合您需求的数据结构。
2015年

2
严格来说,是的,但是如果您需要这些属性,它们可能会带来好处-这就是我的意思。
Patrick Oscity

@PatrickOscity:在这种情况下,肯定可以将它们更好地归类为需求吗?
Lightness Races in Orbit

11
@greggreg拥有关键字列表还有另一个隐含的好处:我们区分结构化和非结构化数据。映射对于具有一组已知键和关键字的结构化数据非常有用。如今,大多数地图用途都是用于结构化数据,而关键字则是可选的。如果我们只有地图,我认为这种区别的很大一部分将会消失。
2015年

1
事实上也如此,地图是从二郎18.要走的路
Papipo

12

关键字列表的主要好处是与现有elixir和erlang代码库的向后兼容性。

如果用作函数自变量,它们还会添加语法糖,类似于红宝石语法:

def some_fun(arg, opts \\ []), do: ...
some_fun arg, opt1: 1, opt2: 2

使用关键字列表的主要缺点是无法对它们执行部分模式匹配:

iex(1)> m = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(2)> %{a: a} = m
%{a: 1, b: 2}
iex(3)> a
1
iex(4)> k = [a: 1, b: 2]
[a: 1, b: 2]
iex(5)> [a: a] = k
** (MatchError) no match of right hand side value: [a: 1, b: 2]

让我们将其扩展到函数参数。假设我们需要基于以下选项之一的值来处理多子句函数:

def fun1(arg, opt1: opt1) when is_nil(opt1), do: do_special_thing
def fun1(arg, opts), do: do_regular_thing

def fun2(arg, %{opt1: opt1}) when is_nil(opt1), do: do_special_thing
def fun2(arg, opts), do: do_regular_thing

这将永远不会执行do_special_thing

fun1("arg", opt1: nil, opt2: "some value")
doing regular thing  

使用map参数,它将起作用:

fun2("arg", %{opt1: nil, opt2: "some value"})
doing special thing

2

映射只允许为一个特定键输入一个条目,而关键字列表则允许重复键。地图是有效的(尤其是随着地图的增长),它们可用于Elixir的模式匹配。

通常,对诸如命令行参数和传递选项之类的内容使用关键字列表,并在需要关联数组时使用映射(或其他数据结构,HashDict)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.