Clojure:具有可更新状态的映射函数

Clojure:具有可更新状态的映射函数,clojure,Clojure,在函数应用程序与序列的每个元素之间实现映射函数以及可更新状态的最佳方法是什么?为了说明这个问题,我们假设我们有以下问题: 我有一个数字向量。我想要一个新的序列,其中每个元素乘以2,然后在序列中添加10个,直到并包括当前元素。例如: [20 30 40 10 20 10 30] 我想生成: [40 60 80 21 41 22 62] 在不增加10的情况下,可以使用高抽象级别来制定解决方案: (map #(* 2 %) [20 30 40 10 20 10 30]) 由于计数需要更新,我不得

在函数应用程序与序列的每个元素之间实现映射函数以及可更新状态的最佳方法是什么?为了说明这个问题,我们假设我们有以下问题:

我有一个数字向量。我想要一个新的序列,其中每个元素乘以2,然后在序列中添加10个,直到并包括当前元素。例如:

[20 30 40 10 20 10 30]
我想生成:

[40 60 80 21 41 22 62]
在不增加10的情况下,可以使用高抽象级别来制定解决方案:

(map #(* 2 %) [20 30 40 10 20 10 30])
由于计数需要更新,我不得不转到basic,我提出的解决方案是:

(defn my-update-state [x state]
  (if (= x 10) (+ state 1) state)
  )

(defn my-combine-with-state [x state]
  (+ x state))

(defn map-and-update-state [vec fun state update-state combine-with-state]
  (when-not (empty? vec)
    (let [[f & other] vec
          g (fun f)
          new-state (update-state f state)]
      (cons (combine-with-state g new-state) (map-and-update-state other fun new-state update-state combine-with-state))
      )))


(map-and-update-state [20 30 40 50 10 20 10 30 ] #(* 2 %) 0 my-update-state my-combine-with-state )
我的问题:这是解决问题的适当/规范的方法,还是我忽略了一些重要的概念/功能

附言:

最初的问题是遍历AST抽象语法树并生成新的AST,同时更新符号表,因此在提出上述问题的解决方案时,请记住这一点

我不担心栈被炸毁,所以用loop+recur替换是不可能的 我关心的是这里

使用全局变量或引用而不是将状态作为参数传递是肯定的否吗

您可以使用reduce累积到目前为止看到的10s数和结果的当前向量

(defn map-update [v]
  (letfn [(update [[ntens v] i]
             (let [ntens (+ ntens (if (= 10 i) 1 0))]
               [ntens (conj v (+ ntens (* 2 i)))]))]
    (second (reduce update [0 []] v))))
您可以使用reduce累积到目前为止看到的10s数和结果的当前向量

(defn map-update [v]
  (letfn [(update [[ntens v] i]
             (let [ntens (+ ntens (if (= 10 i) 1 0))]
               [ntens (conj v (+ ntens (* 2 i)))]))]
    (second (reduce update [0 []] v))))

你可以数到10

(defn count-10[col]
  (reductions + (map #(if (= % 10) 1 0) col)))
例如:

user=> (count-10 [1 2 10 20 30 10 1])
(0 0 1 1 1 2 2)
然后是最终结果的简单地图

(map + col  col (count-10 col)))

你可以数到10

(defn count-10[col]
  (reductions + (map #(if (= % 10) 1 0) col)))
例如:

user=> (count-10 [1 2 10 20 30 10 1])
(0 0 1 1 1 2 2)
然后是最终结果的简单地图

(map + col  col (count-10 col)))

Reduce和reduces是遍历序列以保持状态的好方法。如果您觉得您的代码不清晰,您可以始终将递归与loop/recur或lazy seq一起使用,如下所示

(defn twice-plus-ntens
  ([coll] (twice-plus-ntens coll 0))
  ([coll ntens]
    (lazy-seq
      (when-let [s (seq coll)]
        (let [item (first s)
              new-ntens (if (= 10 item) (inc ntens) ntens)]
          (cons (+ (* 2 item) new-ntens) 
                (twice-plus-ntens (rest s) new-ntens)))))))
看看地图源代码,在你的repl上评估它

(source map)
我跳过了分块优化和多集合支持

你可以这样把它变成一个高阶函数

(defn map-update
  ([mf uf coll] (map-update mf uf (uf) coll))
  ([mf uf val coll]
    (lazy-seq
      (when-let [s (seq coll)]
        (let [item (first s)
              new-status (uf item val)]
          (cons (mf item new-status) 
                (map-update mf uf new-status (rest s))))))))

(defn update-f
  ([] 0)
  ([item status]
    (if (= item 10) (inc status) status)))

(defn map-f [item status]
  (+ (* 2 item) status))

(map-update map-f update-f in)

Reduce和reduces是遍历序列以保持状态的好方法。如果您觉得您的代码不清晰,您可以始终将递归与loop/recur或lazy seq一起使用,如下所示

(defn twice-plus-ntens
  ([coll] (twice-plus-ntens coll 0))
  ([coll ntens]
    (lazy-seq
      (when-let [s (seq coll)]
        (let [item (first s)
              new-ntens (if (= 10 item) (inc ntens) ntens)]
          (cons (+ (* 2 item) new-ntens) 
                (twice-plus-ntens (rest s) new-ntens)))))))
看看地图源代码,在你的repl上评估它

(source map)
我跳过了分块优化和多集合支持

你可以这样把它变成一个高阶函数

(defn map-update
  ([mf uf coll] (map-update mf uf (uf) coll))
  ([mf uf val coll]
    (lazy-seq
      (when-let [s (seq coll)]
        (let [item (first s)
              new-status (uf item val)]
          (cons (mf item new-status) 
                (map-update mf uf new-status (rest s))))))))

(defn update-f
  ([] 0)
  ([item status]
    (if (= item 10) (inc status) status)))

(defn map-f [item status]
  (+ (* 2 item) status))

(map-update map-f update-f in)

最合适的方法是将函数与状态一起使用

(into
  []
  (map
    (let [mem (atom 0)]
      (fn [val]
        (when (== val 10) (swap! mem inc))
        (+ @mem (* val 2)))))
  [20 30 40 10 20 10 30])
也看到

memoize

标准函数

最合适的方法是将函数与状态一起使用

(into
  []
  (map
    (let [mem (atom 0)]
      (fn [val]
        (when (== val 10) (swap! mem inc))
        (+ @mem (* val 2)))))
  [20 30 40 10 20 10 30])
也看到

memoize

标准函数

您的方法适用于一个简单的演示问题,但我不知道如何将其扩展到AST流程,而无需对元素进行两次处理。向量中的每个元素都应该使用从前一个节点更新的状态进行处理,更新算法实际上可能相当复杂。在你的方法中,我需要处理向量两次。第一次运行用于生成状态序列,第二次运行用于将此状态序列与更新向量合并在一起。您是否注意到两次处理向量会导致性能显著下降?这是惯用的方法。如果在评测中,您发现重复序列是您的瓶颈所在,那么是时候进行加速了。我没有衡量实际性能,我怀疑它不会成为瓶颈。为了先得到一个更新的状态,然后再得到实际的结果,两次做同样的事情是不对的。我决定使用另一个答案中提到的reduce函数。您的方法适用于一个简单的演示问题,但我不知道如何在不处理元素两次的情况下将其扩展到AST进程。向量中的每个元素都应该使用从前一个节点更新的状态进行处理,更新算法实际上可能相当复杂。在你的方法中,我需要处理向量两次。第一次运行用于生成状态序列,第二次运行用于将此状态序列与更新向量合并在一起。您是否注意到两次处理向量会导致性能显著下降?这是惯用的方法。如果在评测中,您发现重复序列是您的瓶颈所在,那么是时候进行加速了。我没有衡量实际性能,我怀疑它不会成为瓶颈。为了先得到一个更新的状态,然后再得到实际的结果,两次做同样的事情是不对的。我决定使用另一个答案中提到的reduce函数。谢谢。我真的应该在虚词中扭曲我的思想,以使我的思想图片适合于reduce函数:您的方法对AST处理有效。谢谢。我真的应该在虚词中绞尽脑汁,将我的脑海中的画面融入到reduce函数中:你的方法适用于AST处理。使用全局可变状态不是否定的,但最好避免。特别是对这个
像这样的NG,您可以在其中编写函数。但是,Clojure并不是纯粹的,所以如果使用ref或atoms可以通过各种方式改进代码,那么就使用它们吧。使用全局可变状态并不是一个禁忌,但最好避免。特别是对于像这样可以组合函数的东西。但是,Clojure并不是纯粹的,所以如果有ref或atoms可以通过各种方式改进代码,请使用它们。