从clojure中的序列中获取元素

Ada*_*deg 7 clojure nested-lists lazy-sequences

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

(nth [2 4] 0) ;=> 2
(nth '(2 4) 0) ;=> 2
(get [2 4] 0) ;=> 2
(get '(2 4) 0) ;=> nil -- wtf???
Run Code Online (Sandbox Code Playgroud)

会话的文档get映射一个键,但它适用于矢量或集合很好.nth甚至提到的文档,get仅在边缘情况下讨论它们之间的差异.

我遇到这种奇怪行为的真实情况是我加载了一个yaml文件.它产生了一个嵌套的地图和列表结构.我想用一个元素访问get-in.

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

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

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

Mic*_*zyk 15

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

列表不提供对元素的随机访问; 它们意味着线性遍历.由于支持的访问模式是如此不同,列表和向量绝对不能互换使用,核心Clojure集合库不会支持这种用法.(nth是一个奇怪的例子,它执行低性能的常数或对数时间查找线性遍历; Clojure土地上的一个奇怪的野兽).

当然,与"修改"(在持久性数据结构意义上:创建修改后的副本)有更多不同之处,例如conj工作方式和assoc向量的可用性(如脚注中已提到的;替换元素)列表涉及重建整个前缀直到那一点).

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


1实际上,向量更多是正确的:它们是关联的,所以你可以使用它们assoc((assoc [0 1 2] 0 :foo)返回[:foo 1 2];只(count the-vector)支持assoc索引,对于已经存在于向量中并且紧接着结束的索引).

2出于本讨论的目的,可以考虑使用集合将其成员映射到自己.这在Clojure中实际上是正确的,因为作为函数使用的集合在应用于成员时会返回成员本身 - nil对于非成员 - 并且在某种意义上,这就是实现在幕后的实现.


A. *_*ebb 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"
Run Code Online (Sandbox Code Playgroud)