如何将Common Lisp代码的循环部分转换为Clojure? ......功能定位

时间:2014-12-16 12:25:54

标签: loops clojure functional-programming lisp

如何将这个有效的Common Lisp(SBCL v.1.2.3)代码的循环部分翻译成Clojure(v.1.6)?经过几个小时/几天没有结果,我有点沮丧。在某些地方,我认为我没有得到这种功能定位...

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Unconditional Entropy
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Probabilities
(setq list_occur_prob '()) ;; init

;; set probabilities for which we want to calculate the entropy
(setq list_occur_prob '(1/2 1/3 1/6)) ;;

;; Function to calculate the unconditional
;; entropy H = -sigma i=0,n (pi*log2(pi)
;; bits persymbol.
(setq entropy 0) ;; init
(setq entropy (loop for i in list_occur_prob
   for y = (* (log_base2 i) i)
     collect y
                 ))
(setq entropy (* -1 (apply '+ entropy))) ;; change the sign

;; Print the unconditional entropy in bits per symbol.
(print entropy) ;; BTW, here the entropy is 1.4591479 bits per symbol.

4 个答案:

答案 0 :(得分:5)

在深入研究代码的Clojure之前,您应该花一些时间来清理Common Lisp代码。使用setq你的方式最好被认为是糟糕的风格,并且可能导致最坏的后果:setq旨在为变量赋值,但你的变量{{1} }和list_occur_prob未定义(通过entropy)。此外,这段代码看起来像是你要分配全局变量(再次参见defvar),这些变量是动态变量,按照惯例,它应该用耳罩标记,例如: defvar

然而,对于这一小段代码,你也可以使用像这样通过*entropy*.引入的本地非动态变量(警告,我没有任何CL或Clojure环境方便):

let

有很多方法可以将 (let ((list_occur_prob '(1/2 1/3 1/6))) (loop for i in list_occur_prob for y = (* (log_base 2 i) i) collect y into acc finally (return (* -1 (apply '+ acc))))) 子句优化到循环中:

apply

现在,Daniel Neal已经向您展示了一个基于map / reduce的解决方案,这里使用递归方法更接近原始循环结构:

(let ((list-occur-prob '(1/2 1/3 1/6)))
  (- (loop for i in list-occur-prob
           sum (* (log i 2) i))))

我们正在使用 (defn ent-helper [probs acc] (if (seq probs) (recur (rest probs) (conj acc (* (log_base 2 (first probs)) (first probs)))) acc)) (let [probs 1/2 1/3 1/6 acc (ent-helper probs [])] (* -1 (apply + acc)) 代替conj将结果收集到累加器中。通过collect递归调用实际上为ent-helper的所有值触发的probs调用采用(最初为空)第二个参数,其中到目前为止构建的值被收集。如果我们已经耗尽了所有概率,我们只需返回收集的值。

同样,总结到目前为止的值可以优化到循环中,而不是映射到值。

答案 1 :(得分:4)

我将从更多功能 Common Lisp代码开始:

(- (reduce #'+
           '(1/2 1/3 1/6)
           :key (lambda (i)
                  (* (log i 2) i))))

可以在Lisp中编写命令式代码,有很多操作设置变量值,但它不是最好的风格。

即使紧张的LOOP看起来也不错:

(- (loop for i in '(1/2 1/3 1/6)
         sum (* (log i 2) i)))

答案 2 :(得分:3)

他们需要的键操作是map,它使用函数转换序列。 在您给出的熵示例中,以下内容应该有效:

(def probabilities [1/2 1/3 1/6]) 

(defn log [base x]
  (/ (Math/log x) (Math/log base))) 

(defn entropy [probabilities]
    (->> probabilities
         (map #(* (log 2 %) %)) ; note  - #(f %) is shorthand for (fn [x] (f x))
         (reduce +)
         (-))) 

(entropy probabilities)  ; => 1.459

使用集合时,经常使用管道运算符(->>) 清楚地显示一系列操作。我个人觉得它比嵌套的括号语法更容易阅读,特别是如果有很多操作。

在这里,我们首先在序列上映射pi * log2(pi)函数, 然后使用(reduce +)

对其求和

答案 3 :(得分:1)

我支持schaueho's answer的一般风格,但如果你愿意,你可以更接近"感觉"使用Clojure for宏的循环方法:

(apply - 0
       (for [prob [1/2 1/3 1/6]]
         (* (log prob 2) prob)))

我发现这比schaueho的手动递归版本更容易阅读,而且它的表现要好得多,因为它没有两次遍历列表,也没有将结果累积到临时矢量,等等。

请注意(- (apply + xs))(apply - 0 xs)相同,但您发现哪一个更清楚可能是品味问题。另外,我假设您已经在其他地方定义了合适的log函数。