Loops 如何将公共Lisp代码的循环部分转换为Clojure。。。功能定位

Loops 如何将公共Lisp代码的循环部分转换为Clojure。。。功能定位,loops,clojure,functional-programming,lisp,Loops,Clojure,Functional Programming,Lisp,如何将这个通用Lisp SBCL v.1.2.3代码的循环部分转换为Clojure v.1.6?在做了几小时/几天没有结果之后,我有点沮丧。在某个地方我没有得到这个功能定位,我想 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Unconditional Entropy ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

如何将这个通用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.

您需要的关键操作是使用函数变换序列的映射。 在您给出的熵示例中,以下内容应该适用:

(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*log2pi函数,
然后使用reduce+

对其进行求和,在深入研究Clojure等价的代码之前,您应该花一些时间清理常见的Lisp代码。使用setq你这样做最多被认为是不好的风格,最坏的情况下可能导致未定义的后果:setq旨在为变量赋值,但变量列表\u发生\u先证熵不是通过defvar定义的。此外,这段代码看起来像是在分配全局变量cf。同样,它是动态变量,按照惯例应该用耳罩标记,例如*entropy*

但是,对于这段小代码,您也可以使用通过let引入的本地非动态变量,如下面的警告,我手头没有任何CL或Clojure环境:

 (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子句优化到循环中:

(let ((list-occur-prob '(1/2 1/3 1/6)))
  (- (loop for i in list-occur-prob
           sum (* (log i 2) i))))
现在,Daniel Neal已经向您展示了一个基于map/reduce的解决方案,下面是一个更接近原始循环构造的解决方案,使用递归方法:

 (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的调用本质上是通过recur递归调用为probs的所有值触发的,它采用一个初始为空的第二个参数,在该参数中收集到迄今为止积累的值。如果我们已经用尽了所有的可能性,我们只需返回收集到的值

同样,到目前为止,对值的求和可以优化到循环中,而不是映射到值上。

我将从更具功能的通用Lisp代码开始:

(- (reduce #'+
           '(1/2 1/3 1/6)
           :key (lambda (i)
                  (* (log i 2) i))))
您可以用Lisp编写命令式代码,其中有许多操作设置变量值,但这不是最好的样式

即使是很紧的环也可以看起来很好:

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

我支持的一般风格,但如果您愿意,可以使用Clojure的for macro更接近循环方法的感觉:

我发现这比schaueho的手动递归版本更容易阅读,而且它的性能也更好,因为它不会遍历列表两次,不会将结果累积到临时向量中,等等


请注意,-apply+xs与apply-0xs相同,不过您觉得哪一个更清晰可能是口味的问题。另外,我假设您已经在别处定义了一个合适的日志函数。

作为奖励,代码的功能化版本不仅要短得多,而且更容易翻译成Clojure。*-1 map+acc应该做什么?当然,将一个序列乘以-1没有任何好处,map+xs就是xs,因为+x就是x。我想你指的是更像apply-0acc的东西,这是一种写更长的-apply+acc的巧妙方式。正如我写的,我手边没有一个REPL,所以我想我一定是从原来的-1*apply+acc复制错了。我会更新答案。
(apply - 0
       (for [prob [1/2 1/3 1/6]]
         (* (log prob 2) prob)))