为什么在 Clojure 的瞬态映射中插入 1000 000 个值会生成一个包含 8 个项目的映射? [英] Why inserting 1000 000 values in a transient map in Clojure yields a map with 8 items in it?

查看:22
本文介绍了为什么在 Clojure 的瞬态映射中插入 1000 000 个值会生成一个包含 8 个项目的映射?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我尝试对瞬态向量执行 1000 000 个 assoc!,我将得到一个包含 1000 000 个元素的向量

If I try to do 1000 000 assoc! on a transient vector, I'll get a vector of 1000 000 elements

(count
  (let [m (transient [])]
    (dotimes [i 1000000]
      (assoc! m i i)) (persistent! m)))
; => 1000000

另一方面,如果我对地图做同样的事情,它只会有 8 个项目

on the other hand, if I do the same with a map, it will only have 8 items in it

(count
  (let [m (transient {})]
    (dotimes [i 1000000]
      (assoc! m i i)) (persistent! m)))
; => 8

发生这种情况有什么原因吗?

Is there a reason why this is happening?

推荐答案

瞬态数据类型的操作不保证它们将返回与传入的相同的引用.有时实现可能决定返回一个新的(但仍然是瞬态的)映射在 assoc! 之后,而不是使用你传入的那个.

The transient datatypes' operations don't guarantee that they will return the same reference as the one passed in. Sometimes the implementation might decide to return a new (but still transient) map after an assoc! rather than using the one you passed in.

assoc! 上的 ClojureDocs 页面有一个 很好的例子 解释了这种行为:

;; The key concept to understand here is that transients are 
;; not meant to be `bashed in place`; always use the value 
;; returned by either assoc! or other functions that operate
;; on transients.

(defn merge2
  "An example implementation of `merge` using transients."
  [x y]
  (persistent! (reduce
                (fn [res [k v]] (assoc! res k v))
                (transient x)
                y)))

;; Why always use the return value, and not the original?  Because the return
;; value might be a different object than the original.  The implementation
;; of Clojure transients in some cases changes the internal representation
;; of a transient collection (e.g. when it reaches a certain size).  In such
;; cases, if you continue to try modifying the original object, the results
;; will be incorrect.

;; Think of transients like persistent collections in how you write code to
;; update them, except unlike persistent collections, the original collection
;; you passed in should be treated as having an undefined value.  Only the return
;; value is predictable.

我想重复最后一部分,因为它非常重要:您传入的原始集合应该被视为具有未定义的值.只有返回值是可预测的.

I'd like to repeat that last part because it's very important: the original collection you passed in should be treated as having an undefined value. Only the return value is predictable.

这是您的代码的修改版本,可以按预期工作:

Here's a modified version of your code that works as expected:

(count
  (let [m (transient {})]
    (persistent!
      (reduce (fn [acc i] (assoc! acc i i))
              m (range 1000000)))))

<小时>

顺便说一下,你总是得到 8 的原因是因为 Clojure 喜欢使用 clojure.lang.PersistentArrayMap(一个由数组支持的映射)来处理具有 8 个或更少元素的映射.一旦超过 8,它就会切换到 clojure.lang.PersistentHashMap.


As a side note, the reason you always get 8 is because Clojure likes to use a clojure.lang.PersistentArrayMap (a map backed by an array) for maps with 8 or fewer elements. Once you get past 8, it switches to clojure.lang.PersistentHashMap.

user=> (type '{1 a 2 a 3 a 4 a 5 a 6 a 7 a 8 a})
clojure.lang.PersistentArrayMap
user=> (type '{1 a 2 a 3 a 4 a 5 a 6 a 7 a 8 a 9 a})
clojure.lang.PersistentHashMap

一旦超过 8 个条目,您的瞬态映射会将支持数据结构从成对数组 (PersistentArrayMap) 切换到哈希表 (PersistentHashMap),此时assoc! 返回一个新的引用而不是更新旧的引用.

Once you get past 8 entries, your transient map switches the backing data structure from an array of pairs (PersistentArrayMap) to a hashtable (PersistentHashMap), at which point assoc! returns a new reference instead of just updating the old one.

这篇关于为什么在 Clojure 的瞬态映射中插入 1000 000 个值会生成一个包含 8 个项目的映射?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆