进入与分区

时间:2018-01-27 03:54:23

标签: clojure lazy-evaluation

这是有道理的:

user=> (into {} [[:a 1] [:b 2]])
{:a 1, :b 2}

但为什么会产生错误?

user=> (into {} (partition 2 [:a 1 :b 2]))

ClassCastException clojure.lang.Keyword cannot be cast to java.util.Map$Entry  clojure.lang.ATransientMap.conj (ATransientMap.java:44)

只是为了确定:

user=> (partition 2 [:a 1 :b 2])
((:a 1) (:b 2))

into是否存在延迟序列的问题?如果是这样,为什么?

除了解释为什么这不起作用之外,建议将一系列键值对(例如[:a 1 :b 2])合并到地图中的方法是什么? (apply conj似乎也不起作用。)

3 个答案:

答案 0 :(得分:4)

您可以apply将序列assoc

(apply assoc {:foo 1} [:a 1 :b 2])
=> {:foo 1, :a 1, :b 2}
  

是否存在延迟序列的问题?如果是这样,为什么?

不,into通常与懒惰评估的序列一起使用。这是懒惰的,但每个键/值元组都是一个向量,这就是为什么当into将对减少到地图时它起作用的原因:

(into {} (map vector (range 3) (repeat :x)))
=> {0 :x, 1 :x, 2 :x}

这不起作用,因为键/值对是列表:

(into {} (map list (range 3) (repeat :x)))

所以区别不是懒惰;这是因为into在地图上使用reduce使用conj,这只适用于 vector 键/值对(或MapEntry s):

(conj {} [:a 1]) ;; ok
(conj {} (MapEntry. :a 1)) ;; ok
(conj {} '(:a 1)) ;; not ok

更新:assoc包装器,用于应用注释中建议的空/零序列:

(defn assoc*
  ([m] m)
  ([m k v & kvs]
   (apply assoc m k v kvs)))

答案 1 :(得分:2)

推荐的方法 - (假设seq arg非空,如OP指出的那样) -

Clojure 1.9.0
user=> (apply assoc {} [:a 1 :b 2])
{:a 1, :b 2}

partition版本不起作用,因为partition返回的块是seqs,当conj'到地图时,这些块不会被视为地图条目和实际的地图条目是。

E.g。 (into {} (map vec) (partition 2 [:a 1 :b 2]))会起作用,因为这里的对在conj之前转换为向量。

除非有一些使assoc方便的特殊情况(例如,如果您有一堆传感器要用于预处理{{1},那么使用into的方法仍然是可取的。生成的对等。)。

答案 2 :(得分:1)

Clojure将2-vec(例如[:a 1])视为等同于MapEntry,执行相当于"自动类型转换"。我尽量避免这种情况并始终明确。

(first {:a 1})        => <#clojure.lang.MapEntry [:a 1]>
(conj {:a 1} [:b 2])  => <#clojure.lang.PersistentArrayMap {:a 1, :b 2}>

所以我们看到一个MapEntry打印得像一个矢量但是有一个不同的类型(就像一个Clojure seq打印像list但有不同的类型)。 seq将Clojure地图转换为MapEntry序列,first将我们作为第一个(大多数Clojure函数在任何其他处理之前对任何输入集合调用(seq ...))。

请注意,conj执行逆类型转换,将 向量 [:b 2]视为MapEntry。但是,conj无法为listseq执行自动类型转换:

(throws? (conj {:a 1} '(:b 2)))
(throws? (into {:a 1} '(:b 2)))

into有同样的问题since it is basically just (reduce conj <1st-arg> <2nd-seq>)

其他答案已有3种方法可供使用:

(assoc {}   :b 2)             => {:b 2}
(conj  {}  [:b 2])            => {:b 2}
(into  {} [[:a 1] [:b 2]])    => {:a 1, :b 2}

但是,我会避免这些并坚持hash-mapsorted-map,这两者都避免了空输入seqs的问题:

(apply hash-map [])           => {}            ; works for empty input seq
(apply hash-map [:a 1 :b 2])  => {:b 2, :a 1}

如果输入序列是对的列表,flatten有时会有用:

(apply sorted-map (flatten  [[:a 1] [:b 2]])) => {:a 1, :b 2}
(apply   hash-map (flatten '((:a 1) (:b 2)))) => {:a 1, :b 2}

P.S。

请注意,这些不一样:

  • java.util.Map $ Entry(在jdk docs中列为&#34; Map.Entry&#34;)
  • clojure.lang.MapEntry

P.P.S

如果您已有地图并希望合并(可能为空)键值对序列,请使用intohash-map的组合:

(into {:a 1} (apply hash-map []))       => {:a 1}
(into {:a 1} (apply hash-map [:b 2]))   => {:a 1, :b 2}