从Common Lisp可变操作到Clojure不可变:自定义数据结构数字递增问题
Clojure 实现方案与思维转换建议
最符合Clojure风格的纯不可变实现
你的需求本质是统计transition序列中各二元组的出现次数,完全不需要用可变原子即可实现,代码无副作用,可以在任意函数、let语句中直接使用:
;; 入参为初始计数表count-mx和待统计的transitions序列,直接返回更新后的计数表 (defn update-transition-counts [init-counts transitions] (let [freq-map (frequencies transitions)] (mapv (fn [[k1 k2 init-cnt]] [k1 k2 (+ init-cnt (get freq-map [k1 k2] 0))]) init-counts))) ;; 调用示例 (def updated-count-mx (update-transition-counts count-mx transitions))
这种写法利用Clojure的不可变数据特性,输入得到确定输出,没有隐式状态修改,调试和复用都更简单。
若确实需要可变状态(需跨多次调用保留计数)
建议把计数结构调整为原子包裹的哈希表,更新效率为O(1),远高于原有遍历查找的写法:
;; 初始化:把原有count-mx转成以二元组为key、计数为value的哈希表,用原子包裹 (def count-state (atom (into {} (map (fn [[k1 k2 cnt]] [[k1 k2] cnt]) count-mx)))) ;; 批量更新transitions计数 (doseq [t transitions] (swap! count-state update t inc)) ;; 如需转回你原来的向量格式,直接转换即可 (def updated-count-mx (mapv (fn [[k v]] (conj k v)) @count-state))
从Common Lisp可变思维转换的核心要点
- 优先区分纯计算逻辑和状态持有:纯计算场景一律用不可变数据,通过输入输出传递结果,不要上来就用可变变量存储中间结果
- 真的需要可变状态时,尽量粗粒度管理:不要给每个计数项单独设置原子,统一存在一个状态容器里,维护成本更低、并发安全性更高
- 熟练使用Clojure内置的集合高阶函数:
frequencies、group-by、update、merge-with等函数已经覆盖了大部分统计、更新场景,不需要手动遍历修改元素
原有写法的修复方案
你原来的代码无法在函数/let中执行,是因为你用了惰性的map执行带副作用的swap!操作,没有触发求值的话副作用不会生效,把map换成主动求值的doseq即可运行,但仍然不推荐这种O(n)查找的低效率写法:
(doseq [x transitions] (swap! (third (some #(when (= x (vec (first %) (second %))) %) count-mx)) inc))
内容的提问来源于stack exchange,提问作者analogsamurai
相关产品推荐
相关产品推荐

