如何从clojure中的另一个函数中追加变量?

时间:2013-07-17 11:00:43

标签: clojure

问题并没有真正解释我想做什么,但我想不出别的什么。

我在一段代码中的外let函数中有一个空映射,以及一个整数数组。 我想遍历整数数组,执行一个简单的任务,并继续将结果映射附加到外部变量中的变量。

(let [a {}  ;outer variables
      b {}]
  (doseq [x [1 2 3]]
         (let [r (merge a {x (* x x)}) ;I want to append this to a
               s (merge b {x (+ x x)})] ;and this to b
           (println (str "--a--" r "--b--" s)))))

但是一旦我离开doseq,我的ab变量仍然是空的。我认为a和b的范围不会延伸到doseq之外,因为它可以保持从内部进行的任何更改并且它们是不可变的。

在这种情况下,如何计算a和b的值?我尝试将doseq的功能提取到另一个函数中并使用:

调用let
(let [a (do-that-function)])
等等,但即便如此,我也无法找到一种方法来跟踪剂量循环中的所有修改,然后作为一个整体发回。

我是以错误的方式接近这个吗?

由于


修改

真的,我要做的是:

(let [a (doseq [x [1 2 3]] {x (* x x)})]
  (println a))

但是doseq返回nil所以a将是nil:-s

2 个答案:

答案 0 :(得分:3)

clojure中的所有变量都是不可变的。如果您需要可变状态,则应使用atomsrefs

但在您的情况下,您只需从doseq切换到for

(let [a (for [x [1 2 3]] {x (* x x)})]
  (println a))

以下是使用原子解决问题的示例:

(let [a (atom {})
      b (atom {})]
  (doseq [x [1 2 3]]
    (swap! a assoc x (* x x))
    (swap! b assoc x (+ x x)))
  (println "a:" @a)
  (println "b:" @b))

但你应该尽量避免使用可变状态:

(let [l [1 2 3]
      a (zipmap l (map * l l))
      b (zipmap l (map + l l))]
  (println "a:" a)
  (println "b:" b))

答案 1 :(得分:1)

诀窍在于考虑添加到现有数据的数据流来制作新数据,而不是改变过去的数据。对于您正在构建数据结构的特定问题,通常使用reduce:

(reduce (fn [result x] (assoc result x (* x x))) {} [1 2 3])
嘿嘿,我只是注意到“减少”可能看起来令人困惑,因为它正在构建一些东西,但意思是一系列东西被“减少”为一件事。在这种情况下,我们给出一个空的map来开始,它会绑定到fn中的结果,并且每个连续的映射都会产生一个新的结果,我们再次使用assoc添加它。

你也可以说:

(into {} (map (fn [x] [x (* x x)]) [1 2 3]))

在您的问题中,您希望从单个集合中同时制作多个内容。这是一种方法:

(reduce (fn [[a b] x] [(assoc a x (* x x)) (assoc b x (+ x x))]) [{} {}] [1 2 3])

这里我们使用解构语法来引用我们的两个结果结构 - 只是用[矢量]]来描述数据。请注意,reduce仍然只返回一件事 - 在这种情况下是一个向量。

而且,我们可以概括一下:

(defn xfn [n fs] 
  (reduce 
    (fn [results x] (map (fn [r f] (assoc r x (f x x))) results fs)) 
      (repeat (count fs) {}) (range n)))

=> (xfn 4 [* + -])
({3 9, 2 4, 1 1, 0 0} {3 6, 2 4, 1 2, 0 0} {3 0, 2 0, 1 0, 0 0})

结果是地图列表。如果您想在构建这些结果时采取中间步骤,则可以将reduce更改为reduce。通常,map用于转换集合,reduce用于构建集合中的单个结果。