gpt4 book ai didi

clojure - 在我自己的数据结构中递增数字时 clojure 中的不变性,从常见的 lisp 可变性到 clojure 不变性

转载 作者:行者123 更新时间:2023-12-04 22:34:17 25 4
gpt4 key购买 nike

在对 lisp 进行更多研究之前,我已经使用了一些 clojure,我想重新回到 clojure 中......

所以我有这个共同的 lisp:

(mapcar #'(lambda (x)
(incf (third (car (member x count-mx
:test #'equal
:key #'(lambda (x) (list (car x) (cadr x))))))))
transitions)

对我来说,在 clojure 中翻译成(不确定这是否是好的 clojure):

(map (fn [x] (swap! (third (some #(if (= x (vec (first %) (second %))) %)
count-mx))
inc))
transitions)

其中 count-mx,如果我使用原子来生成零:

[[a a 0] [a b 0] [a c 0] [a d 0] [b a 0] [b b 0] [b c 0] [b d 0] [c a 0] [c b 0] [c c 0] [c d 0] [d a 0] [d b 0] [d c 0] [d d 0]]

和过渡:

[[a a] [a b] [b c] [c d] [d a] [a b] [b c]]

目标是在映射过渡并看到 [a a] 之后将 [a a 0] 增加到 [a a 1]

虽然 clojure 行确实有效,但由于不可变性,我不能在函数或 let 中使用它,我如何摆脱我常见的 lisp 思维?我非常喜欢在某些内容中映射和修改值,但我不确定如何在 clojure 中高效和/或正确地执行此操作。

最佳答案

编辑(改进答案)

我不知道: map 也接受向量作为键。在这种情况下,我们可以直接将对作为键,并使用 update 结合 inc 来更新值(通过将值增加 1)。我使用 sorted-map 因为它看起来更适合演示(排序)- 性能更高的方法是使用 (hash-map){}into 表达式中。

(def m (into (sorted-map)        ;; or: `{}` btw `(hash-map)`
(for [x '[a b c d] ;; nesting for-loop
y '[a b c d]] ;; for sunccinct creation
{[x y] 0}))) ;; of all combinations

m
;; => {[a a] 0, [a b] 0, [a c] 0, [a d] 0,
;; [b a] 0, [b b] 0, [b c] 0, [b d] 0,
;; [c a] 0, [c b] 0, [c c] 0, [c d] 0,
;; [d a] 0, [d b] 0, [d c] 0, [d d] 0}


(def transitions '[[a a] [a b] [b c] [c d]
[d a] [a b] [b c]])

(def m' (reduce (fn [a-map a-transition]
(update a-map a-transition inc))
m
transitions))

m'
;; => {[a a] 1, [a b] 2, [a c] 0, [a d] 0,
;; [b a] 0, [b b] 0, [b c] 2, [b d] 0,
;; [c a] 0, [c b] 0, [c c] 0, [c d] 1,
;; [d a] 1, [d b] 0, [d c] 0, [d d] 0}

(reduce (fn [mp key] (update mp key inc)) m '[a b c]) 扩展为:(update (update (update m 'a inc) 'b inc) 'c inc)因此(以累积的方式)顺序更新 transitions 中给定的每个键的值。

我们通过以下方式将排序后的映射转换为嵌套向量形式:

(def m'' (into [] (map (comp vec flatten) (seq m'))))

m''
;; => [[a a 1] [a b 2] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 2] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 1]
;; [d a 1] [d b 0] [d c 0] [d d 0]]


我们可以将它们归纳为函数:

(defn update-map-by-transitions 
"Update the map `m` successively by transitions"
[m transitions & {:keys [func] :or {func inc}}]
(reduce (fn [m' tr] (update m' tr func))
m
transitions))

(defn map-to-vecs
"Transform transition map to flatten vectors"
[m & {:keys [vec-func] :or {vec-func flatten}}]
(into [] (map (comp vec vec-func) (seq m))))

(def m' (update-map-by-transitions m transitions))
(def m'' (map-to-vecs m' :vec-func flatten))

m''
;; [[a a 1] [a b 2] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 2] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 1]
;; [d a 1] [d b 0] [d c 0] [d d 0]]

;; or do:
(def m''' (map-to-vecs m' :vec-func identity))
m'''
;; [[[a a] 1] [[a b] 2] [[a c] 0] [[a d] 0]
;; [[b a] 0] [[b b] 0] [[b c] 2] [[b d] 0]
;; [[c a] 0] [[c b] 0] [[c c] 0] [[c d] 1]
;; [[d a] 1] [[d b] 0] [[d c] 0] [[d d] 0]]

原始答案

我也来自 Common Lisp。顺便说一下,您将其缩写为 CL 或“Lisp”。但 CLISP 实际上意味着 CL 的众多实现之一(仅次于 sbcl、abcl、ECL 等)。所以不要调用 CL clisp ...(以避免其他人对此感到恼火)。

我认为使用嵌套向量来完成这项任务很乏味,而且在以后扩展时性能会降低。

相反,更喜欢 map 。它们具有 update 函数,在嵌套映射的情况下,update-in 可以操作和“改变”映射的值,这对于这项任务来说非常方便。

要获取 vec [a b c d] 并生成 {a 0 b 0 c 0 d 0},我将定义:

(defn mapper [vec default-val]
(into {} (for [x vec] {x default-val})))

(mapper '[a b] 0) ;; => {a 0 b 0}

现在,我们需要两个键来存储它们的计数值:嵌套映射:

(def m (mapper '[a b c d] (mapper '[a b c d] 0)))

m
;; =>{a {a 0, b 0, c 0, d 0},
;; b {a 0, b 0, c 0, d 0},
;; c {a 0, b 0, c 0, d 0},
;; d {a 0, b 0, c 0, d 0}}

我们可以随时将它们转换回您喜欢的嵌套矢量形式(我承认,它更易于人类阅读):

(defn nested-maps-to-vecs [nested-map]
(vector (for [[k v] nested-map
[ik iv] v]
[k ik iv])))

(nested-maps-to-vecs m)
;;=> [[a a 0] [a b 0] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 0] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 0]
;; [d a 0] [d b 0] [d c 0] [d d 0]]

现在定义transitions:

(def transitions '[[a a] [a b] [b c] [c d] [d a] [a b] [b c]])

使用 inc 函数进行更新和方便的 update-in 我们可以“改变” map 的值:

(def m' (reduce (fn [a-map a-transition] (update-in a-map a-transition inc)) m transitions))

m'
;; => {a {a 1, b 2, c 0, d 0},
;; b {a 0, b 0, c 2, d 0},
;; c {a 0, b 0, c 0, d 1},
;; d {a 1, b 0, c 0, d 0}}

(nested-maps-to-vecs m')
;; => [[a a 1] [a b 2] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 2] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 1]
;; [d a 1] [d b 0] [d c 0] [d d 0]]

瞧!

(update-in a-map a-transition inc) 获取例如[a b] 并使用键序列 a 访问嵌套映射,然后访问 b 并获取该“位置”中的值,并且对其应用 inc 并将结果“存储”到该“位置”。好吧,它实际上并没有存储它,但它返回了一个具有更新值的新 map 。这个新 map 是 reduce 函数中的新 a-map 并且采用下一个 a-transition 这个更新的 map 将进一步更新。因此,reduce 是在遍历 transitions 序列时通过不断更新和捕获更新 map 来“累积”更新的技巧。

Clojure 有一种非常有效的方法来“更新”其不可变结构。它仅保存更改,同时引用未更改的其余部分。因此,update 的这种“生成新的更新 map ”听起来比实际情况更糟:它实际上是高效的性能 - 以及内存占用方面。由于可变性,Common Lisp 和其他 Lisp 或语言无法采用这种特殊的存储策略。只有像 Clojure 或 Haskell 这样具有确保数据不变性的纯函数式语言才能使用这种策略来存储和更新其不可变数据。

然而,Clojure 在 atom 中也具有可变性,必须明确声明为 atom。在这种情况下,使用 atom 作为映射或嵌套向量中的值并不是 Clojure 的方式。

我还在努力思考这一切。

旅途愉快!

关于clojure - 在我自己的数据结构中递增数字时 clojure 中的不变性,从常见的 lisp 可变性到 clojure 不变性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69670388/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com