夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

Clojure勉強日記(その16 より「書かずに」すませる方法

こんにちは。とりあえず遅延シーケンスを体験したので、
次はよりClojureの機能を使って簡単に済ませる方法のようです。

何回パターンが発生する?

下記のようなベクタを考えて、
[:A :B :B :A :A :B]
A → Aのようなパターンが発生する回数が何回あるかを考えてみると。。。2回。

こういう判定をやってくれる関数を考えるとこうなります。

(defn count-pattern [vect]
  (loop [cnt 0 vect vect]
    (if (empty? vect) cnt
    (recur (if (and (= :A (first vect)) (= :A (second vect))) (inc cnt) cnt) (rest vect)))))
#'user/count-pattern
user=> (count-pattern [:A :B :B :A :A :B])
2

なのですが、これは失格コードの模様。
シーケンスライブラリの扱い方と、「問題を細分化する」を考えると・・・・
とりあえず、問題を細分化してみましょう。
「A → Bのようなパターンが発生する回数が何回あるか」?は、つまりは、
「与えられたシーケンスのうち[:A :B]という部分集合が何回あるか?」ということです。

ということはシーケンスライブラリの中から要素数2の部分集合を抽出し、
それを比較すればいいということになりますね。

そのため、「シーケンスライブラリの中から要素数2の部分集合を抽出」という関数を考えてみます。

すると・・・以下のようになります。

(defn coll-pairs [coll]
    (let [take-pair #(when (next %) (take 2 %))]
        (lazy-seq
          (when-let [pair (seq (take-pair coll))]
            (cons pair (coll-pairs (rest coll)))))))
#'user/coll-pairs
user=> (coll-pairs [:A :B :B :A :A :B])
((:A :B) (:B :B) (:B :A) (:A :A) (:A :B))

後はそれぞれのパーツを検証するのみと。

user=> (defn count-pattern-pair [vect]
    (count (filter (fn [elem] (and (= :A (first elem)) (= :B (second elem)))) (coll-pairs vect))))
#'user/count-pattern-pair
user=> (count-pattern-pair [:A :B :B :A :A :B])
2

で、おなじみですがClojureには同種の処理を行う関数がある模様。
やっている内容的には先ほど作った関数をより汎用化したようなノリですね。

(defn partition
  "Returns a lazy sequence of lists of n items each, at offsets step
  apart. If step is not supplied, defaults to n, i.e. the partitions
  do not overlap. If a pad collection is supplied, use its elements as
  necessary to complete last partition upto n items. In case there are
  not enough padding elements, return a partition with less than n items."
  {:added "1.0"
   :static true}
  ([n coll]
     (partition n n coll))
  ([n step coll]
     (lazy-seq
       (when-let [s (seq coll)]
         (let [p (doall (take n s))]
           (when (= n (count p))
             (cons p (partition n step (nthrest s step))))))))
  ([n step pad coll]
     (lazy-seq
       (when-let [s (seq coll)]
         (let [p (doall (take n s))]
           (if (= n (count p))
             (cons p (partition n step pad (nthrest s step)))
             (list (take n (concat p pad)))))))))

partitionを使用すると下記のようになります。
これを組み合わせればより簡単に関数は書けそうですね。

user=> (partition 2 [:A :B :B :A :A :B])
((:A :B) (:B :A) (:A :B))
user=> (partition 2 1 [:A :B :B :A :A :B])
((:A :B) (:B :B) (:B :A) (:A :A) (:A :B))
カリー化と部分適用

一つの引数を受け取り、元の関数の呼び出しにおいてその引数が固定されたような新たな関数を作ること・・
をカリー化と呼ぶそうなのですが、やはりさっぱりなので実際のコードを書いてみます。
まずは、引数を後から指定できる「部分適用」とい動作をするpartialを使ったパターン。
fnのパターンと違い、[]や%による定義がなくても引数に関数が適用されています。

user=> (def add3 (partial + 3))
#'user/add3
user=> (add3 1000)
1003

で、これがカリー化部分適用を用いたパターン。
・・・うん、ぶっちゃけ違いわかりません(汗

user=> (defn faux-curry [& args] (apply partial partial args))
#'user/faux-curry
user=> (def add-3 ((faux-curry +) 3))
#'user/add-3
user=> (add-3 1000)
1003

実際本を読んでいるとClojureにおいてはカリー化と部分適用を同じ意味に使っているそうなので、
引数リストの一部分を事前に与えることが可能な関数・・・と考えておけばいいようです。