clojure从零开始(五)

1. Pulling It All Together

用以前的各个篇幅的知识, 创建小例子:smacking around hobbits!打孩子身边霍比特人! 

为了打一个hobbit,我们首先模拟其身体部位。每个身体部位包括它的相对大小,以帮助我们确定它是如何可能的是这部分将受到打击。 


为了避免重复,这霍比特模式将仅包括“左脚”项目,“左耳”等,因此,我们需要一个功能完全的对称化模型。 


最后,我们将创建一个遍历我们的身体部位,并随机选择一击的功能。 

1.1. The Shire's Next Top Model

对于我们的 hobbit 的模型,我们会避开这种特性为“joviality”和“mischievousness”和只注重hobbit的微小的身体。下面是我们的hobbit模型:

(def asym-hobbit-body-parts [{:name "head" :size 3}
                             {:name "left-eye" :size 1}
                             {:name "left-ear" :size 1}
                             {:name "mouth" :size 1}
                             {:name "nose" :size 1}
                             {:name "neck" :size 2}
                             {:name "left-shoulder" :size 3}
                             {:name "left-upper-arm" :size 3}
                             {:name "chest" :size 10}
                             {:name "back" :size 10}
                             {:name "left-forearm" :size 3}
                             {:name "abdomen" :size 6}
                             {:name "left-kidney" :size 1}
                             {:name "left-hand" :size 2}
                             {:name "left-knee" :size 2}
                             {:name "left-thigh" :size 4}
                             {:name "left-lower-leg" :size 3}
                             {:name "left-achilles" :size 1}
                             {:name "left-foot" :size 2}])
This is a vector of maps.每个映射具有主体部分的名称和主体部分的相对大小。例如:动画人物眼睛有1/3的他们的头的大小
defn has-matching-part?
  [part]
  (re-find #"^left-" (:name part)))

(defn matching-part
  [part]
  {:name (clojure.string/replace (:name part) #"^left-" "right-")
   :size (:size part)})

(defn symmetrize-body-parts
  "Expects a seq of maps which have a :name and :size"
  [asym-body-parts]
  (loop [remaining-asym-parts asym-body-parts
         final-body-parts []]
    (if (empty? remaining-asym-parts)
      final-body-parts
      (let [[part & remaining] remaining-asym-parts
            final-body-parts (conj final-body-parts part)]
        (if (has-matching-part? part)
          (recur remaining (conj final-body-parts (matching-part part)))
          (recur remaining final-body-parts))))))

(symmetrize-body-parts asym-hobbit-body-parts)
; => the following is the return value
[{:name "head", :size 3}
 {:name "left-eye", :size 1}
 {:name "right-eye", :size 1}
 {:name "left-ear", :size 1}
 {:name "right-ear", :size 1}
 {:name "mouth", :size 1}
 {:name "nose", :size 1}
 {:name "neck", :size 2}
 {:name "left-shoulder", :size 3}
 {:name "right-shoulder", :size 3}
 {:name "left-upper-arm", :size 3}
 {:name "right-upper-arm", :size 3}
 {:name "chest", :size 10}
 {:name "back", :size 10}
 {:name "left-forearm", :size 3}
 {:name "right-forearm", :size 3}
 {:name "abdomen", :size 6}
 {:name "left-kidney", :size 1}
 {:name "right-kidney", :size 1}
 {:name "left-hand", :size 2}
 {:name "right-hand", :size 2}
 {:name "left-knee", :size 2}
 {:name "right-knee", :size 2}
 {:name "left-thigh", :size 4}
 {:name "right-thigh", :size 4}
 {:name "left-lower-leg", :size 3}
 {:name "right-lower-leg", :size 3}
 {:name "left-achilles", :size 1}
 {:name "right-achilles", :size 1}
 {:name "left-foot", :size 2}
 {:name "right-foot", :size 2}]

1.2. let

上面有这样的话:

(let [[part & remaining] remaining-asym-parts
      final-body-parts (conj final-body-parts part)]
  some-stuff)
左边绑定右边

一个简单的例子:

(let [x 3]
  x)
; => 3


(def dalmatian-list
  ["Pongo" "Perdita" "Puppy 1" "Puppy 2"]) ; and 97 more...
(let [dalmatians (take 2 dalmatian-list)]
  dalmatians)
; => ("Pongo" "Perdita")
let也使用于新范围
(def x 0)
(let [x 1] x)
; => 1
还可以参考现有绑定
(def x 0)
(let [x (inc x)] x)
; => 1
let的好处
1.通过let命名的东西清晰 
2.它们允许计算一次表达式,再重复利用的结果。同样重要的是,当表达式具有副作用。
;; Associate "part" with the first element of "remaining-asym-parts"
;; Associate "remaining" with the rest of the elements in "remaining-asym-parts"
;; Associate "final-body-parts" with the result of (conj final-body-parts part)
(let [[part & remaining] remaining-asym-parts
      final-body-parts (conj final-body-parts part)]
  (if (has-matching-part? part)
    (recur remaining (conj final-body-parts (matching-part part)))
    (recur remaining final-body-parts)))
注意 partremaining, 和 final-body-parts let中每一个都可以利用多次. 如果不用 partremaining, and final-body-parts 会很复杂,例如:
(if (has-matching-part? (first remaining-asym-parts))
  (recur (rest remaining-asym-parts)
         (conj (conj (conj final-body-parts part) (first remaining-asym-parts))
               (matching-part (first remaining-asym-parts))))
  (recur (rest remaining-asym-parts)
         (conj (conj final-body-parts part) (first remaining-asym-parts))))
let是一个将name和value联系起来的方式

1.3. loop循环

(loop [iteration 0]
  (println (str "Iteration " iteration))
  (if (> iteration 3)
    (println "Goodbye!")
    (recur (inc iteration))))
; =>
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Goodbye!
第一行 loop [iteration 0] 开始循环并介绍绑定初始值,就像用default value去调用匿名函数 . 第一次遍历, iteration 值为0
其次,它打印一个小消息。 

然后,它会检查迭代的价值 ---如果它大于3,那么现在是时候说再见了。否则,我们再循环。
这就像调用由loop产生的匿名函数,但这次我们把它传递一个参数,(inc iteration).。 

下面例子可以完成同样的事情:
(defn recursive-printer
  ([]
     (recursive-printer 0))
  ([iteration]
     (println iteration)
     (if (> iteration 3)
       (println "Goodbye!")
       (recursive-printer (inc iteration)))))
(recursive-printer)
; =>
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Goodbye!

1.4. Regular Expressions正则表达式 

正则表达式是用于对文本进行模式匹配工具。我不会进入他们的工作,但这里是他们的文字符号:

;; pound, open quote, close quote
#"regular-expression"
在第一个model处理中,有re-find 返回true or false 根据part's name是否starts with "left-":
(defn has-matching-part?
  [part]
  (re-find #"^left-" (:name part)))
(has-matching-part? {:name "left-eye"})
; => true
(has-matching-part? {:name "neckbeard"})
; => false
matching-part用正则表达式来用"right-"替换"left-" 
(defn matching-part
  [part]
  {:name (clojure.string/replace (:name part) #"^left-" "right-")
   :size (:size part)})
(matching-part {:name "left-eye" :size 1})
; => {:name "right-eye" :size 1}]

1.5. Symmetrizer

重点分析下 symmetrizer 。

(def asym-hobbit-body-parts [{:name "head" :size 3}
                             {:name "left-eye" :size 1}
                             {:name "left-ear" :size 1}
                             {:name "mouth" :size 1}
                             {:name "nose" :size 1}
                             {:name "neck" :size 2}
                             {:name "left-shoulder" :size 3}
                             {:name "left-upper-arm" :size 3}
                             {:name "chest" :size 10}
                             {:name "back" :size 10}
                             {:name "left-forearm" :size 3}
                             {:name "abdomen" :size 6}
                             {:name "left-kidney" :size 1}
                             {:name "left-hand" :size 2}
                             {:name "left-knee" :size 2}
                             {:name "left-thigh" :size 4}
                             {:name "left-lower-leg" :size 3}
                             {:name "left-achilles" :size 1}
                             {:name "left-foot" :size 2}])

(defn has-matching-part?
  [part]
  (re-find #"^left-" (:name part)))

(defn matching-part
  [part]
  {:name (clojure.string/replace (:name part) #"^left-" "right-")
   :size (:size part)})

; ~~~1~~~
(defn symmetrize-body-parts
  "Expects a seq of maps which have a :name and :size"
  [asym-body-parts] ; 
  (loop [remaining-asym-parts asym-body-parts ; ~~~2~~~
         final-body-parts []]
    (if (empty? remaining-asym-parts) ; ~~~3~~~
      final-body-parts
      (let [[part & remaining] remaining-asym-parts ; ~~~4~~~
            final-body-parts (conj final-body-parts part)]
        (if (has-matching-part? part) ; ~~~5~~~
          (recur remaining (conj final-body-parts (matching-part part))) ; ~~~6~~~
          (recur remaining final-body-parts))))))
1.该功能采用了是常见于函数式编程的一般的策略。给定序列(a vector of body parts and their sizes ),连续的序列分割成一个“ head”和“ tail”。处理head,将它添加到了一些成绩,然后用递归来继续该过程的tail。 

2. 循环开始在body parts。该序列的“tail”将被绑定到remaining-asym-parts。最初,它被绑定到完整序列传递给function,remaining-asym-parts。创建一个结果序列,final-body-parts;它的初始值是一个空vector。 
3. 如果remaining-asym-parts 是空的,这意味着我们处理整个序列,并且可以返回结果 final-body-parts。 

4. 否则,split the list into a head, part, and tail, remaining另外, 向 final-body-parts添加 part ,然后重新绑定结果final-body-parts.  

5.   如果是这样,matching-part 加至final-body-parts and recur(循环),否则,只是recur。

1.6. Shorter Symmetrizer with Reduce

形式"process each element in a sequence and build a result"用 reduce函数。例如:

;; sum with reduce
(reduce + [1 2 3 4])
; => 10

就等于clojure中的

(+ (+ (+ 1 2) 3) 4)

因此, reduce works by doing this:

  1. 给定的函数应用于序列的前两个元素。这就是(+12)从何而来。
  2. 给定的函数应用到的结果与该序列的下一个元素。在例子中,步骤1的结果为3,并且该序列的下一个元素是3为好。所以,你最终得到(+3 3) 。
  3. 对序列中的每一个剩余的元素重复步骤2。 Reduce 也可选的初始值.       15是这里的初始值:

 Reduce 也可选的初始值.       15是这里的初始值:

(reduce + 15 [1 2 3 4])
如果提供的初始值,然后通过施加给定的函数的初始值和所述序列的第一个元素开始reduce,而不是该序列的前两个元素。 
为了进一步了解reduce如何工作,这里有一个方法,它可以实现:

(defn my-reduce
  ([f initial coll]
     (loop [result initial
            remaining coll]
       (let [[current & rest] remaining]
         (if (empty? remaining)
           result
           (recur (f result current) rest)))))
  ([f [head & tail]]
     (my-reduce f (f head (first tail)) (rest tail))))
我们可以重新实现 symmetrize,如下所示:
(defn better-symmetrize-body-parts
  "Expects a seq of maps which have a :name and :size"
  [asym-body-parts]
  (reduce (fn [final-body-parts part]
            (let [final-body-parts (conj final-body-parts part)]
              (if (has-matching-part? part)
                (conj final-body-parts (matching-part part))
                final-body-parts)))
          []
          asym-body-parts))

1.7. Hobbit Violence

现在,让我们创建一个函数,将决定 which part of the hobbit被击中: 

(defn hit
  [asym-body-parts]
  (let [sym-parts (better-symmetrize-body-parts asym-body-parts)
        body-part-size-sum (reduce + 0 (map :size sym-parts))
        target (inc (rand body-part-size-sum))]
    (loop [[part & rest] sym-parts
           accumulated-size (:size part)]
      (if (> accumulated-size target)
        part
        (recur rest (+ accumulated-size (:size part)))))))

(hit asym-hobbit-body-parts)
; => {:name "right-upper-arm", :size 3}

(hit asym-hobbit-body-parts)
; => {:name "chest", :size 10}

(hit asym-hobbit-body-parts)
; => {:name "left-eye", :size 1}






你可能感兴趣的:(clojure从零开始(五))