【问题标题】:Clojure: map function with updatable stateClojure:具有可更新状态的映射函数
【发布时间】:2015-02-20 16:55:26
【问题描述】:

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

我有一个数字向量。我想要一个新序列,其中每个元素乘以 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])

计数更新迫使我“进入基本”,我想出的解决方案是:

(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 )

我的问题:这是解决问题的适当/规范方法还是我忽略了一些重要的概念/功能。

PS:

  1. 原来的问题是遍历 AST(抽象语法树),生成新的 AST 和更新符号表,所以在提出上述问题的解决方案时请牢记。

  2. 我不担心炸毁堆栈,所以用循环+递归替换不是 我在这里担心。

  3. 使用全局 Vars 或 Refs 而不是将 state 作为参数传递是否是明确的禁忌?

【问题讨论】:

  • 使用全局可变状态不是禁忌,但最好避免。特别是对于像这样你可以组合函数的事情。但是,Clojure 并不是纯粹的,所以如果有 refs 或 atom 可以通过各种方式改进您的代码,请使用它们。

标签: clojure


【解决方案1】:

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

(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))))

【讨论】:

  • 谢谢。我真的应该在功能词上扭曲我的想法,以使我的心理图片适合减少功能:) 你的方法适用于 AST 处理。
【解决方案2】:

要计算 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)))

【讨论】:

  • 您的方法适用于一个简单的演示问题,但我看不出如何在不处理元素两次的情况下将其扩展到 AST 过程。向量中的每个元素都应该使用从前一个节点更新的状态进行处理,并且更新算法实际上可能非常复杂。在你的方法中,我需要处理两次向量。第一次运行用于生成状态序列,第二次运行将该状态序列与更新向量合并。
  • 您是否注意到两次处理向量的性能显着下降?
  • 这是(the/an)惯用的方法。如果在分析中,您发现重复序列是您的瓶颈所在,那么是时候加快速度了。
  • 我没有测量实际性能,我怀疑它不会成为瓶颈。两次做同样的事情以先获得更新的状态然后再获得实际结果只是“感觉不对”。我决定使用另一个答案中提到的“减少”功能。
【解决方案3】:

归约和归约是遍历保持状态的序列的好方法。如果你觉得你的代码不清楚,你总是可以像这样使用循环/递归或惰性序列的递归

(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)

【讨论】:

    【解决方案4】:

    最合适的方式是使用带状态的函数

    (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
    

    标准函数

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多