8

我知道 Clojure 中的列表和向量在大多数情况下几乎可以互换使用。这是一个让我惊讶的简单案例

(nth [2 4] 0) ;=> 2
(nth '(2 4) 0) ;=> 2
(get [2 4] 0) ;=> 2
(get '(2 4) 0) ;=> nil -- wtf???

会谈的文档get映射一个键,但它适用于向量或集合就好了。nth甚至提到的文档,get仅在边缘情况下谈论它们的差异。

我遇到这种奇怪行为的实际情况是我加载了一个 yaml 文件。它导致了地图和列表的嵌套结构。我想用get-in.

(def form (parse-yaml some-yaml-file))
(def best-friend (get-in form [:friends 0 :first-name]))

它不起作用,因为在内部get-in使用get。所以我有一个理论和实际的问题:

  • 这种行为被get认为是正确的和预期的吗?如果是这样,请解释原因。
  • 如何访问这种地图和列表结构中的嵌套元素?
4

2 回答 2

17

的行为get是正确和预期的。get适用于“键控”数据结构,其中值映射到键。这包括向量,将索引映射到值1并设置2

列表不提供对元素的随机访问;它们应该被线性遍历。由于支持的访问模式如此不同,列表和向量绝对不能互换使用,核心 Clojure 集合库不努力支持这种用法。(nth是一个奇怪的函数示例,它确实执行了低性能的常数或对数时间查找线性遍历;Clojure 领域的一个奇怪的野兽)。

当然,与“修改”(在持久数据结构意义上:创建修改后的副本)还有进一步的区别,例如工作方式conj和 for 向量的可用性assoc(如脚注中已经提到的;替换一个列表涉及到重建整个前缀直到那个点)。

如果您想对数据使用类似向量的访问模式,则应将其放入向量中。列表可以转换为向量(以线性时间)vec。如果您正在处理一种序列化格式,其中对于某些数据是否应该返回列表或向量是不明确的,并且您的解析器不接受告诉它应该使用哪个选项,您可能必须自己进行一些后处理(clojure.walk可能有用,尤其是prewalkandpostwalk函数;假设只涉及基本的 Clojure 数据类型)。


1事实上,向量更是如此:它们是关联的,因此您可以将它们与assoc( (assoc [0 1 2] 0 :foo)returns [:foo 1 2]; 仅(count the-vector)支持 up to 的索引一起使用,for associng 到向量中已经存在并立即超过结尾的索引)。

2就本次讨论而言,可以认为集合将其成员映射到自身。这在 Clojure 中实际上是正确的,因为用作函数的集合在应用于它时返回成员本身 - 以及nil对于非成员 - 并且从某种意义上说,这就是实现在引擎盖下的样子。

于 2013-05-18T11:46:06.517 回答
5

Michał Marczyk 出色答案的代码示例补充:

(def form
  {:friends
  '({:id 1, :first-name "bob"}
    {:id 2, :first-name "sue"})
   :languages
  '({:id 1, :name "Clojure"})})

(-> form :friends (nth 0) :first-name)
;=> "bob"

(def form'
  (clojure.walk/prewalk #(if (list? %) (vec %) %) form))

(get-in form' [:friends 0 :first-name])
;=> "bob"
于 2013-05-18T14:43:48.203 回答