Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2008-07-11

サンプルコードによるLOOPマクロ入門 (10)

| 13:07 | サンプルコードによるLOOPマクロ入門 (10) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (10) - わだばLisperになる

initially節とfinally節

繰り返し処理の流れとしては、

  1. 変数の初期化等の前準備
  2. 繰り返し本体
  3. 後始末

等が多いかと思いますが、LOOPでは、initiallyで、前準備の節を、finallyで後始末の節を区切ることができます。

  • :finally

後始末を:finallyキーワードの後に書くと繰り返し終了時にまとめて実行されます。

(loop :repeat 5 
      :collect (random 10) :into rs
      :collect (gensym) :into gs
      :finally (return (list rs gs)))
;=> ((5 3 3 0 6) (#:G2112 #:G2113 #:G2114 #:G2115 #:G2116))

返り値を返したい場合は、明示的に(return)する必要があります。

  • :initially

:initiallyは、他の構文で代用できることが多いためか、実際の使用例は殆ど見掛けません。

あえて、

(prog (list ans)
      (setq list '(1 2 3 4 5))
  L   (cond ((null list) (return (nreverse ans))))
      (push (* 2 (pop list)) ans)
      (go L))
;=> (2 4 6 8 10)

;; ※はるか昔のPROGでは、変数宣言と、初期化は同時にできなかった

のような古えのPROGを使った繰り返しのイディオムをそのまま移植したりするのには便利かもしれません。

(loop :with list :and ans
      :initially (setq list '(1 2 3 4 5))
      :when (null list) :return (nreverse ans)
      :do (push (* 2 (pop list)) ans))
;=> (2 4 6 8 10)

2008-07-10

サンプルコードによるLOOPマクロ入門 (9)

| 19:54 | サンプルコードによるLOOPマクロ入門 (9) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (9) - わだばLisperになる

繰り返し途中での脱出等

繰り返しの中で脱出したい場合は、良くあります。

この場合、(return)や使える場所では:returnを使用して繰り返しから抜けることができます。

これら以外にも脱出の常套句的なものが用意されています。

  • :while

次の式の値がNILの場合即座にLOOPを終了させます

集積の結果や、:finally節は実行されます。

(loop :as i :upfrom 0 :while (< i 10)
      :collect i)
;=> (0 1 2 3 4 5 6 7 8 9)
  • :until

次の式の値が非NILの場合即座にLOOPを終了させます。

集積の結果や、:finally節は実行されます。

(loop :as i :upfrom 0 :until (>= i 10)
      :collect i)
;=> (0 1 2 3 4 5 6 7 8 9)

以上、2つは、脱出というよりは、LOOPの終了条件といった感じです。

  • :always

次の式の値がNILの場合即座にLOOPから抜けます。脱出した場合のLOOPの返り値は、NILですが、途中脱出しなかった場合は、Tになります。

集積の結果や、:finally節は実行されません。

(defun list= (x y)
  (loop :for xx :in x
        :for yy :in y
        :always (eql xx yy)))

(list= '(1 2 3 4) '(1 2 3 4))
;=> T
  • :never

次の式の値が非NILの場合即座にLOOPから抜けます。脱出した場合のLOOPの返り値は、NILですが、途中脱出しなかった場合は、Tになります。

集積の結果や、:finally節は実行されません。

(loop :never 1)
;=> NIL
  • :thereis

次の式の値が非NILの場合即座にLOOPから抜けます。脱出した場合のLOOPの返り値は、式の値ですが、途中脱出しなかった場合は、NILになります。

集積の結果や、:finally節は実行されません。

(loop :thereis 1)
;=> 1

その他

CLでは、someや、every等がありますが、CL以前のMacLISPには存在しないということもあり、

(if (some #'oddp '(1 2 3 4))
    'foo
    'bar)
;=> FOO

(if (loop :for x :in '(1 2 3 4) :thereis (oddp x))
    'foo
    'bar)
;=> FOO

と書いたような例がちらほらあります。

サンプルコードによるLOOPマクロ入門 (8)

| 18:27 | サンプルコードによるLOOPマクロ入門 (8) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (8) - わだばLisperになる

for/as色々

全く計画性もなく書いてるので話題が前後してしまいますが、値を生成する:for節は色々な書き方ができます。

また、:forは別名で:asとも書けます。

初期値〜更新

DOマクロのように初期化〜更新を組で書くことができます。

この場合、:=と、:thenを組で使用します。

(loop :as i := 0 :then (1+ i)
      :repeat 10            ;これがないと止まりません。
      :collect i)
;=> (0 1 2 3 4 5 6 7 8 9)

更新部分と初期化部分が同じ場合、更新部分を省略できます。

(loop :for i := 0       ;毎度0が代入される
      :repeat 10
      :collect i 
      :do (setq i nil)) ;iをnilに変更して邪魔をする。
;=> (0 0 0 0 0 0 0 0 0 0)

サンプルコードによるLOOPマクロ入門 (7)

| 14:22 | サンプルコードによるLOOPマクロ入門 (7) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (7) - わだばLisperになる

値の収集 その2

:collectや、:sum等で値を収集する場合、一つだけでなく複数に分散して値を格納したい時があります。

そういう時には、:collect等の後に:intoを付け変数名を指定します。

注意点としては、:intoを指定しない場合は暗黙の収集変数に値が格納され、LOOPを抜けたときにその値がLOOPの結果の値とされますが、:intoで指定した場合は、手動で値を返してやらないといけません。この場合、:finally節で指定するのが一般的なようです。また、:into以下の変数は:into節により適切に初期化されるので、手動で初期化する必要はありません。

(loop :for i :upfrom 0 :upto 10
      :collect i :into all
      :when (oddp i)
        :collect i :into odds
      :else
        :collect i :into evens
      :end
      :sum i :into sum
      :maximize i :into max
      :minimize i :into min
      :finally (return `(all => ,all odds => ,odds evens => ,evens sum => ,sum max => ,max min => ,min)))
;=>
(ALL => (0 1 2 3 4 5 6 7 8 9 10)
 ODDS => (1 3 5 7 9)
 EVENS => (0 2 4 6 8 10)
 SUM => 55
 MAX => 10
 MIN => 0)

はまりどころ

  • finallyで(return)を忘れる
(loop :for i :from 0 :to 10 :collect i :into ans
      :finally ans)
  • せっかく丁寧に収集用の変数を宣言してあげたのに怒られる
(loop :with ans := ()
      :for i :from 0 :to 10 :collect i :into ans
      :finally (return ans))
;>>> Variable ANS in INTO clause is a duplicate

2008-07-06

サンプルコードによるLOOPマクロ入門 (番外編 L-99)

| 15:25 | サンプルコードによるLOOPマクロ入門 (番外編 L-99) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (番外編 L-99) - わだばLisperになる

機能を順に紹介して行くのも良いのですが、実際に手を動かしてみるのも良いだろうということで、意味なくL-99のP25まで、無理にLOOPを使って解いてみました。

「できるだけLOOPマクロ内で完結させる」ということをテーマに書いてみました。

自分はLOOPマクロは苦手でしたが、それでも200行位LOOPばっかり書けば、いい加減馴れて来るようです…。

;; P01
(defun last-pair (list)
  (loop :for x :on list :when (atom (cdr x)) :return x))

(last-pair '(1 2 3 4))
;=> (4)

(last-pair '(1 2 3 . 4))
;=> (3 . 4)

;; P02
(defun last-2-pair (list)
  (loop :for x :on list :when (atom (cddr x)) :return x))

(last-2-pair '(1 2 3 4))
;=> (3 4)

(last-2-pair '(1 2 3 . 4))
;=> (2 3 . 4)

;; P03
(defun element-at (list position)
  (loop :for p := 1 :then (1+ p)
        :for x :in list
        :when (= position p) :return x))

(element-at '(a b c d e) 13)
;=> NIL

(element-at '(a b c d e) 3)
;=> C

;; P04
(defun len (list)
  (loop :for x :in list :count 'T))

(len '(1 2 3 4))
;=> 4

;; P05
(defun rev (list)
  (loop :for a := (copy-list list) :then (prog1 (cdr a) (rplacd a b))
        :and b := ()               :then a
        :when (null a) :return b))

(rev '(1 2 3 4))
;=> (4 3 2 1)

;; P06
(defun palindrome-p (list)
  (loop :for nom :in list
        :and rev :in (reverse list)
        :always (equal nom rev)))     

(palindrome-p '(1 2 3 2 1))
;=> T

;; P07
(defun flatten (list)
  (loop :for x :in list 
        :if (listp x)
          :append (flatten x)
        :else
          :collect x))

(flatten '(1 2 3 (4 5 (6 (7 (8 (9 (((10((((((()))))))))))))))))
;=> (1 2 3 4 5 6 7 8 9 10)

;; P08
(defun compress (list)
  (loop :for x    :in list
        :and prev := (gensym) :then x
        :unless (equal prev x) :collect x))

(compress '(a a a a b c c a a d e e e e))
;=> (A B C A D E)

;; P09
(defun pack (list)
  (loop :for x    :in (nconc (copy-list list) (list (gensym)))
        :and prev := (gensym) :then x
        :and tem  := ()       :then (cons x tem)
        :unless (or (equal prev x) (null tem))
          :collect tem
          :and :do (setq tem () )
        :end))

(pack '(a a a a b c c a a d e e e e e))
;=> ((A A A A) (B) (C C) (A A) (D) (E E E E E))

;; P10
(defun encode (list)
  (loop :for x :in (pack list)
        :collect `(,(length x) ,(car x))))

(encode '(a a a a b c c a a d e e e e))
;=> ((4 A) (1 B) (2 C) (2 A) (1 D) (4 E))

;; P11
(defun encode-modified (list)
  (loop :for x :in (pack list)
        :when (= 1 (length x)) 
          :collect (car x)
        :else
          :collect `(,(length x) ,(car x))))

(encode-modified '(a a a a b c c a a d e e e e))
;=> ((4 A) B (2 C) (2 A) D (4 E))

;; P12
(defun decode (list)
  (loop :for x :in list 
        :when (atom x)
          :collect x
        :else
          :append (make-list (first x) 
                             :initial-element (second x))))

(decode '((4 A) B (2 C) (2 A) D (4 E)))
;=> (A A A A B C C A A D E E E E)

;; P13
(defun encode-direct (list)
  (loop :for x    :in (nconc (copy-list list) (list (gensym)))
        :and prev := (gensym) :then x
        :and tem  := ()       :then (cons x tem)
        :and cnt  := 0        :then (1+ cnt)
        :unless (or (equal prev x) (null tem))
          :when (= 1 cnt) 
            :collect prev 
          :else 
            :collect (list cnt prev)
          :end
          :and :do (setq tem () cnt 0)
        :end))

(encode-direct '(a a a a b c c a a d e e e e))
;=> ((4 A) B (2 C) (2 A) D (4 E))

;; P14 (*) Duplicate the elements of a list.
(defun dupli (list)
  (loop :for x :in list :nconc (list x x)))

(dupli '(a b c c d))
;=> (A A B B C C C C D D)

;; P15
(defun repli (list times)
  (loop :for x :in list 
        :nconc (loop :repeat times :collect x)))

(repli '(a b c) 3)
;=> (A A A B B B C C C)

;; P16
(defun drop (list n)
  (loop :for x :in list
        :and pos :from 1
        :unless (zerop (mod pos n)) :collect x))

(drop '(a b c d e f g h i k) 3)
;=> (A B D E G H K)

;; P17
(defun split (list n)
  (loop :for x :on list
        :for pos :from 1
        :when (> pos n) 
          :do (return-from split (list tem x))
        :else
          :collect (car x) :into tem)
        :end
        :finally (return-from split (list list () )))

(split '(a b c d e f g h i k) 3)
;=> ((A B C) (D E F G H I K))

;; P18
(defun slice (list start end)
  (loop :for x :in list
        :for pos :from 1
        :when (<= start pos end) 
          :collect x :into res
        :finally (return res)))

(slice '(a b c d e f g h i k) 3 7)
;=> (C D E F G)

;; P19 
(defun rotate (list n)
  (loop :with n := (mod n (length list))
        :for x :on list
        :for pos :from 1
        :when (> pos n) 
          :do (return-from rotate (append x tem))
        :else
          :collect (car x) :into tem)
        :end
        :finally (return-from rotate list))

(rotate '(a b c d e f g h) 3)
;=> (D E F G H A B C)

;; P20
(defun remove-at (list n)
  (loop :for x :in list
        :and pos :from 1
        :unless (= pos n) :collect x))

(remove-at '(a b c d) 2)
;=> (A C D)

;; P21
(defun insert-at (item list n)
  (loop :for x :in list
        :and pos :from 1
        :when (= pos n)
          :append (list item x)
        :else 
          :collect x))

(insert-at 'alfa '(a b c d) 2)
;=> (A ALFA B C D)

;; P22
(defun range (start end)
  (loop :for i :from start :to end :collect i))

(range 4 9)
;=> (4 5 6 7 8 9)

;; P23
(defun remove-at (list n)
  "取り除く要素/残りの多値を返すバージョン"
  (loop :for x :in list
        :and pos :from 1
        :unless (= pos n) 
          :collect x :into res
        :else 
          :collect x :into item
        :finally (return-from remove-at (values res item))))

(remove-at '(1 2 3 4) 4)
;=> (1 2 3),(4)

(defun rnd-select (list n)
  (flet ((choose (lst)
           (multiple-value-list 
            (remove-at lst (1+ (random (length lst)))))))
    (loop :for i :from 1 :to (min n (length list))
          :for (tem x) := (choose list) :then (choose tem)
          :append x)))

(rnd-select '(a b c d e f g h) 7)
;=> (H E G F D B C)

;; P24
(defun lotto-select (n range)
  (rnd-select (range 1 range) n))

(lotto-select 6 49)
;=> (14 37 4 8 9 46)

;; P25
(defun rnd-permu (list)
  (rnd-select list (length list)))

(rnd-permu '(a b c d e f))
;=> (A C B F D E)

サンプルコードによるLOOPマクロ入門 (6)

| 07:52 | サンプルコードによるLOOPマクロ入門 (6) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (6) - わだばLisperになる

ローカル変数

LOOP内ではループ内でローカル変数を宣言して使用することができます。

良く

(let ((foo 1)
      (bar 2)
      (baz 3))
  (loop :repeat 5 :collect (list foo bar baz)))

;=> ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3))

のようなコードを見かけますが、これは、

(loop :with foo := 1 :and bar := 2 :and baz := 3
      :repeat 5 :collect (list foo bar baz)))

;=> ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3))

という風に:withを使って書けます。

上の元の例の場合、foo、bar、bazはletを使って並列に束縛されているのですが、この場合、:andでつなぎます。

では、

(let* ((foo 1) 
       (bar 2)
       (baz (* 3 foo)))
  (loop :repeat 5 :collect (list foo bar baz))))

;=> ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3))

のようにlet*で順番に上から束縛する場合はどう書くかというと、

(loop :with foo := 1 :and bar := 2
      :with baz := (* 3 foo)
      :repeat 5 :collect (list foo bar baz))

;=> ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3))

と:withを複数回使って書きます。

LOOPでは、:andが文脈によって意味が変ってくるのですが、どっちがパラレルでどっちがシリアル束縛か忘れてしまったら、マクロ展開をして結果を確認するのがてっとり早いかもしれません。

まとめ

という風にLOOPマクロ内で変数宣言もできるのですが、複雑になると若干読み難くくなるところもあるかもしれません…。

2008-07-02

サンプルコードによるLOOPマクロ入門 (5)

| 15:07 | サンプルコードによるLOOPマクロ入門 (5) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (5) - わだばLisperになる

値の走査/生成

集める方法を先に紹介してしまいましたが、値を走査/生成する方法にも色々あります。

大まかに分けると、数値を生成するものと、リスト/ベクタを走査するものです。

リスト

:inと:onが使えます。:inは、各要素を順番に、:onは、デフォルトで順にcdrを取得します。

(loop :for i :in '(1 2 3 4 5) :collect i)
;=> (1 2 3 4 5)

(loop :for i :on '(1 2 3 4 5) :collect i)
;=> ((1 2 3 4 5) (2 3 4 5) (3 4 5) (4 5) (5))
  • 取得する間隔を変更する

:byの後に関数を指定することで間隔を変更することができます。

デフォルトでは、#'cdrなので、1つおきにしたい場合、#'cddrという風になります。

なんとなく、:inで、#'cddrという指定は直感的でない気もします。

(loop :for i :in '(1 2 3 4 5) :by #'cddr :collect i)
;=> (1 3 5)

(loop :for i :on '(1 2 3 4 5) :by #'cddr :collect i)
;=> ((1 2 3 4 5) (3 4 5) (5))
ベクタ
(loop :for i :across #(1 2 3 4 5) :collect i)
;=> (1 2 3 4 5)

(loop :for i :across "1-2-3-4-5" 
      :when (parse-integer (string i) :junk-allowed 'T) 
        :collect :it)
;=> (1 2 3 4 5)

ベクタの場合は、:inや、:onではなくて、別の:acrossというキーワードを使います。

一々型によって使い分けるのも面倒な気もしますが、マクロ展開時に、型に応じてコードを出力するためらしいので型が変れば違う指定をする位に考えておくと良いかもしれません。とはいえ文字列もベクタなので同じキーワードです。

数値
(loop :for i :from 0 :to 10 :collect i)
;=> (0 1 2 3 4 5 6 7 8 9 10)

(loop :for i :from 0 :to 10 :by 2 :collect i)
;=> (0 2 4 6 8 10)

(loop :for i :from 0 :below 10 :collect i)
;=> (0 1 2 3 4 5 6 7 8 9)

(loop :for i :from 10 :downto 0 :collect i)
;=> (10 9 8 7 6 5 4 3 2 1 0)

(loop :for i :from 10 :above 0 :collect i)
;=> (10 9 8 7 6 5 4 3 2 1)

数値の生成には、:fromや、:toのように範囲を指定すれば、順に増加/減少した数値を取得できます。これも:byによってステップを変更できます。

:from、:downfromが開始の指定、:to、:upto、:downto、:below、:above等色々あって一々面倒ですが、これもマクロ展開ため(キーワードで方向を明示しないと増加方向なのか、減少方向なのか推測できない)ためらしいので、そういうものだと思って暗記するしかないでしょう。

(loop :repeat 10 :for i :from 0 :collect i)
;=> (0 1 2 3 4 5 6 7 8 9)

(loop :repeat 10 :for i :downfrom 100 :by 3 :collect i)
;=> (100 97 94 91 88 85 82 79 76 73)

のように、:repeatと組み合わせれば、終点の指定を省略できます。XからYステップでN個欲しいというような場合には、こっちの方が分かりやすいかもしれません。

2008-07-01

サンプルコードによるLOOPマクロ入門 (4)

| 23:11 | サンプルコードによるLOOPマクロ入門 (4) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (4) - わだばLisperになる

フィルター

:collectを使って値を集めてリストにする方法を取り上げてきましたが、ただ集めるだけでなくて、対象を選別しつつ集めたいこともあります。

そのような場合には、:if、:when、:unless等の述語と:collectとを組み合わせます。

(loop :for i :in '(1 2 3 4 5 6 7) 
      :do (when (oddp i) :collect i))

のように書けば良さそうなものですが、飽くまで:collectは、LOOPのキーワードであり、LOOPの中でしか機能しないので、通常の式の中に組み込んで使うことができません。

ということで、

(loop :for i :in '(1 2 3 4 5 6 7) 
      :when (oddp i) :collect i)
;=> (1 3 5 7)

となります。

また、これらの述語と組み合わせると、:collectでは、:itで述語の結果を参照できるので

(loop :for i :in '(1 2 3 4 5 6 7) :when (and (oddp i) i) :collect :it)
;=> (1 3 5 7)

とも書けます。ちなみに、ここでは、わざわざ:itを使うために述語の部分が冗長になっていますが、

(loop :for i :in '(1 () 2 () 3 () 4) :when i :collect :it)
;=> (1 2 3 4)

という風に、nilか真値を返すような述語を使う場合に便利かもしれません。

また、:if、:when、:unlessは、2系統に分岐することができます。

(loop :for i :in '(1 2 3 4 5 6 7) 
      :when (oddp i)
        :collect i
      :else
        :collect '?
      :end)
;=> (1 ? 3 ? 5 ? 7)

若干ややこしいのですが、LOOPの:whenは、通常のLISPのwhenと違い、:ifとまったく同じなもので単なる別名です。

また、:endというキーワードが使えて区切りをハッキリさせる目的で使えます。

2008-06-27

サンプルコードによるLOOPマクロ入門 (3)

| 16:38 | サンプルコードによるLOOPマクロ入門 (3) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (3) - わだばLisperになる

値の蓄積

LOOPマクロの実際のコードでは、なんらかの値を蓄積してリスト等にして返すものが多いかと思います。

値を蓄積するキーワードとしては、:collect、:sum、:count〜等色々ありますが、実際に使われるのは、:collectが殆どだと思います。

  • :collect
(loop :repeat 5 
      :collect (random 5)
      :collect 'a
      :collect (random 100)
      :collect 'b
      :collect (gensym))
;=> (2 A 41 B #:G3407 1 A 10 B #:G3408 4 A 13 B #:G3409 4 A 6 B #:G3410 0 A 83
     B #:G3411)

何の役にも立たなそうな例ですが、:collectが複数回使えることを強調してみました。

:repeatが急に登場しましたが、これは、単純に指定した回数だけループを回すというものです。

  • :append
(loop :repeat 5 
      :append (list (random 5) 'a (random 100) 'b (gensym)))

上と同じ結果になるものを:appendで作成してみています。

:appendは、関数のappendから類推できるようにリストを継げて行きます。

そのため:append以下はリストになっている必要があります。

  • :nconc
(loop :repeat 5 
      :nconc (list (random 5) 'a (random 100) 'b (gensym)))

:appendはリストを新規に作成して継げてゆきますが、nconcは、既存のものを破壊的に連結して行きます。

上の例では、新規に作られたリストが連結されて行くので問題ありませんが、

(let* ((foo '((a) (b) (c) (d) (e)))
       (bar (copy-tree foo)))
  (loop :for x :in bar
        :nconc x)
  bar))
;=> ((A B C D E) (B C D E) (C D E) (D E) (E))

のような場合に、元リストが変更されるので、取り扱い注意になります。

数値篇

リスト系の操作の他に、数値を扱う操作もあります。

リストに比べると使用頻度は下がるかと思いますが、数値計算をしている場合には、便利なものが多いかもしれません。

Project Eulerのような問題を解くのには割と便利に使えるような気がします。

  • :maximize
(loop :repeat 5 :maximize (random 100))
;=> 76

値の最大値を返します。

  • :minimize
(loop :repeat 5 :minimize (random 100))
;=> 10

値の最小最大値を返します。

(loop :repeat 5 :sum (random 100))
;=> 231

値の合計を返します。

(loop :repeat 5 :count (oddp (random 100)))
;=> 2

:countに与えられた引数が真の場合をカウントします。

2008-06-26

サンプルコードによるLOOPマクロ入門 (2)

| 23:11 | サンプルコードによるLOOPマクロ入門 (2) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (2) - わだばLisperになる

分割(Destructuring)

書くネタがある訳じゃないのですが、とりあえず続きもので行きます。

LOOPマクロの便利な機能として、LOOP変数で分割代入が使えるというのがあります。

for 変数 in

のところに、

for (変数 変数) in

という風に分割したい形式に合せて指定すると、それぞれの変数に分割された内容が代入されます。

(defvar *data* '((0 A) (1 B) (2 C) (3 D) (4 E) (5 F) (6 G) (7 H) (8 I)))
  • 各々の子リストの内容を逆転
(loop :for (x y) :in *data* :collect (list y x))
;=> ((A 0) (B 1) (C 2) (D 3) (E 4) (F 5) (G 6) (H 7) (I 8))
  • ドット対で指定
(loop :for (x . y) :in *data* :collect (list x y))
;=> ((0 (A)) (1 (B)) (2 (C)) (3 (D)) (4 (E)) (5 (F)) (6 (G)) (7 (H)) (8 (I)))
  • 複雑なもの
(defvar *data2* 
  '(((0 (A))) ((1 (B))) ((2 (C))) ((3 (D))) ((4 (E))) ((5 (F))) ((6 (G)))
    ((7 (H))) ((8 (I)))))

(loop :for ((x (y))) :in *data2* :collect x :collect y)
;=> (0 A 1 B 2 C 3 D 4 E 5 F 6 G 7 H 8 I)

:COLLECTは複数回使えます。

  • 他の構文との比較
;; dolistで
(let (res)
  (dolist (x *data2* (nreverse res))
    (destructuring-bind ((x (y))) x
      (push x res)
      (push y res))))

;;mapcanで
(mapcan (lambda (x)
          (destructuring-bind ((x (y))) x
            (list x y)))
        *data2*)

2008-06-25

サンプルコードによるLOOPマクロ入門 (1)

| 17:18 | サンプルコードによるLOOPマクロ入門 (1) - わだばLisperになる を含むブックマーク はてなブックマーク - サンプルコードによるLOOPマクロ入門 (1) - わだばLisperになる

LOOPマクロはCLerのなかでも賛否両論な感じなのですが、それなりに使われていることもあり、簡単なものの読み書き位はできるようになっておいたほうが良いかと思います。

複雑という評判だけに、色々とチュートリアルがあるのですが、実際のところ「どう使うのか」というよりは、機能を列記しているものが多いようで、リファレンス的な内容になっているものが多いようです。

ということで、レシピブックのように使い方に焦点を絞って、あまり使いそうもない機能は、応用ということで紹介してゆくのも良いかと思いました。

タイトルは、Perl入門ゼミさんをパクリました(笑)

LOOPマクロの構文

LOOPマクロは、ミニ言語と呼ばれるようにLOOPの節の中で一つの世界を形成しています。

どういう世界かといえば、中置記法が使える世界で、また、英文としても読み下しやすいように配慮されています。

ということで、LOOPマクロの文法をそれなりに憶える必要があり、色々組み合わせると複雑にもなるのですが、普段使うものは、比較的少なく、数種類のパターンを丸暗記してしまえば、9割方読み書きできるような気がするので、そんなパターンを列記して行くことにします。

見やすさの工夫

LOOPマクロは、中置記法を実現するために色々なキーワードを使っています。

(loop for i in '(1 2 3 4) collect (* i i))

という場合、for、in、collectはキーワードで、iは変数名なのですが、

人によっては、読みやすいようにキーワードを目立つように書く人もいます。

  • キーワードパッケージのシンボルでキーワードを書いた例(ややこしい表現)
(loop :for i :in '(1 2 3 4) :collect i)
  • キーワードを大文字にしてみた例(非常にまれ)
(LOOP FOR i IN '(1 2 3 4) COLLECT (* i i))

LIST -> LISTパターン (in)

LISPだけにリストを操作するパターンが多いと思うのですが、まず、このパターンを紹介してみます。

暗記するキーワードは、forと、inと、collectです。

(loop :for i :in '(1 2 3 4) :collect i)
;=> (1 2 3 4)

'(1 2 3 4)というリストを取って、'(1 2 3 4)というリストを返します。

同じような内容の処理は、

(mapcar (lambda (x) x) '(1 2 3 4))
;=> (1 2 3 4)

とも書けます。

繰り返しの内容としては、'(1 2 3 4)というリストの要素を先頭から順にiに格納し、繰り返します。

それで、:COLLECTというのは、値を収集するという指定で、繰り返しが終わると、その蓄積結果を全体の結果として返します。

ちなみに、:COLLECTを使わないLOOPマクロは、使ってもあまり旨味がないことが殆どで、他の繰り返し構文の方が綺麗に書けることが多いと思います。

応用(COLLECT節で色々実験する)

(loop :for i :in '(1 2 3 4) :collect (list i))
;=> ((1) (2) (3) (4))

(loop :for i :in '(1 2 3 4) :collect (* i i))
;=> (1 4 9 16)

(loop :for i :in '(1 2 3 4)
      :collect (list i '=> (format nil "~R" i)))
((1 => "one") (2 => "two") (3 => "three") (4 => "four"))

;; 元リストと同じ長さで内容がGENSYMのリストを返す
(loop :for i :in '(1 2 3 4) :collect (gensym))
;=> (#:G3362 #:G3363 #:G3364 #:G3365)

他の構文との比較

(loop :for i :in '(1 2 3 4) :collect (list i))
;=> ((1) (2) (3) (4))

;; mapcar
(mapcar #'list '(1 2 3 4))

;; do
(do ((i '(1 2 3 4) (cdr i))
     (res () (cons (list (car i)) res)))
    ((endp i) (nreverse res)))

;; dolist
(let (res)
  (dolist (i '(1 2 3 4) (nreverse res))
    (push (list i) res)))

;; reduce
(reduce (lambda (i res) (append (list i) res))
        '(1 2 3 4) :from-end 'T :initial-value () )

;; reduce #2
(nreverse
 (reduce (lambda (res i) (cons (list i) res))
         '(1 2 3 4) :initial-value () ))
  • mapcar
    • この例の場合、一番シンプル
  • do
    • 蓄積用の変数resを用意しないといけない
    • cdrを降りて行くので、リストのCARを取得する必要がある。
    • consして、最後にnreverseするのがイディオムだが、面倒といえば面倒(appendして順番を保持しても良いが、cons&nreverseの方が効率が良い)
  • dolist
    • 蓄積用の変数resを用意しないといけない
    • 結果をreverseする必要がある
  • reduce
    • do、dolistと同様に自由度は高いが、この程度の処理の場合、逆に繁雑