ファイヤープロジェクト
反復
2004-01-05T08:15+09:00   matsu
反復オペレータdoなどについてもう少し詳細に書いてみた.
do
doの書式を以下に示す.
(do (変数 初期値 更新方法)
    (終了判定式)
    式
    式
    式
    ....)
特に第一引数リストがdoオペレータの肝である.このリストの変数,初期値,更新方法はいずれも省略可能である(※).終了判定式の値が真なら反復を終了する.以下に例を示す.
> (do ((i 0 (+ i 1)))
       ((> i 10))
       (format t "~A" i))
012345678910
NIL
この例では変数iを生成してその初期値を0に設定し,反復の度にiに1を加える.本体ではiの値を出力するが,それはi>10でなければ反復される.
※全てが省略された場合は空リストを書く.
先のdoの例では無駄に括弧が多いような印象を受ける.第一引数と第二引数の部分である.第一引数のリストは変数,初期値,更新方法からなると書いたが,より正確には変数,初期値,更新方法からなるリストのリストである.つまり先の例は第一引数の要素数が1の場合の例である.以下は第一引数リストの要素数を2とした例である.
> (do ((i 0 (+ i 1))
          (j 100 (- j 1)))
         ((> i 10))
         (format t "i = ~A, j = ~A~%" i j))
i = 0, j = 100
i = 1, j = 99
i = 2, j = 98
i = 3, j = 97
i = 4, j = 96
i = 5, j = 95
i = 6, j = 94
i = 7, j = 93
i = 8, j = 92
i = 9, j = 91
i = 10, j = 90
NIL
この例では第一引数のリストにより変数i,jを生成し,iは反復の度に1増,jは反復の度に1減している.
do*
doの第一引数のリスト内に複数のリストがある場合,letとlet*と同様の疑問が生じる.すなわち,doの第一引数リスト内の複数のリスト間で同じ引数を共有した場合どうなるか,という問題である.
> (do ((i 0 (+ i 1))      
(j i i))
((> j 10))
(format t "i=~A,j=~A~%" i j))

*** - EVAL: variable I has no value
この実行例では,Iが値を持っていないと怒られている.このIはjの初期値のiのことである.すなわちjの初期値では一番目の更新式のiを参照していない.
> (setf i 10)      
10
> (do ((i 0 (+ i 1))
(j i i))
((> j 10))
(format t "i=~A,j=~A~%" i j))
i=0,j=10
i=1,j=0
i=2,j=1
i=3,j=2
i=4,j=3
i=5,j=4
i=6,j=5
i=7,j=6
i=8,j=7
i=9,j=8
i=10,j=9
i=11,j=10
NIL
あらかじめiを設定しておくと,jの初期値の設定ができた.上の実行例でiとjのから注意すべき点がわかる.jの更新式では更新前のiの値を参照している.すなわちi,jどちらの更新式でも一つ前の反復のiの値を受け取る(先にIに値がないと怒られたのはこのためであった.).だからdoの第一引数リストの中ではリストの順番は関係ない.同様の問題はletでもあった.そしてletではlet*を使用すればこの問題に対処できた.doではdo*で対処できる.
> (do* ((i 0 (+ i 1))      
(j i i))
((> j 10))
(format t "i=~A,j=~A~%" i j))
i=0,j=0
i=1,j=1
i=2,j=2
i=3,j=3
i=4,j=4
i=5,j=5
i=6,j=6
i=7,j=7
i=8,j=8
i=9,j=9
i=10,j=10
NIL
jの更新式では先行するiの更新式実行後のiの値を参照している.
第二引数リストの終了判定式も第一引数リストと同様に複数の式のリストである.こちらの場合,終了判定式を複数指定できるという意味ではなく(※),第二要素にオプションとしてdo式の返り値を指定できる.
(do ((i 0 (+ i 1))
     (j 100 (- j 1)))
    ((> i 10)
     (< j 95))
    (format t "i = ~A, j = ~A~%" i j))
i = 0, j = 100
i = 1, j = 99
i = 2, j = 98
i = 3, j = 97
i = 4, j = 96
i = 5, j = 95
i = 6, j = 94
i = 7, j = 93
i = 8, j = 92
i = 9, j = 91
i = 10, j = 90
T
この場合,i>10を終了判定に使用し,do式の返り値としてj<95の真偽値を指定している.先の例のとおり,第二引数リストの第二要素を指定しなければnilが返る.
※それをしたければ第二引数リストの第一要素をand式などにする.
dolistはPerlにおけるforeachに似た機能を持つ.
(dolist (変数 リスト 返り値) 式)
dolistではリストの要素を順番にとりだして,変数に格納し,式を実行する.式は複数可.返り値はdolist全体の返り値であるが省略可能で,省略したときはnilが返り値となる.
> (setf sum 0)
0
> (dolist (x '(1 2 3 4 5) sum)
       (format t "sum += ~A~%" (* x x))
       (setf sum (+ sum (* x x))))
sum += 1
sum += 4
sum += 9
sum += 16
sum += 25
55
この例では変数sumに1,2,3,4,5の二乗を順番に足して,その結果のsumをdolistの返り値として返している.
dotimesは,本体式を指定した回数実行する.
(dotimes (変数 回数 返り値) 式)
dotimesでは,0から回数-1までの値を変数に格納し,式を実行する.返り値についてはdolistと同様である.dolistの例と同様の処理をdotimesを使用して書くと以下のようになる.
> (setf sum 0)
0
> (dotimes (x 6 sum)
(format t "sum += ~A~%" (* x x))
(setf sum (+ sum (* x x))))
sum += 0
sum += 1
sum += 4
sum += 9
sum += 16
sum += 25
55
dolistでは0から(回数-1)回反復する点に注意.返り値を以下のように指定すると,反復回数をdotimesの返り値にすることができる.
> (dotimes (x 10000 x))
10000
dolistは一つのリストに対して反復処理を行なう.mapcを使用すると,複数のリストに対して反復処理を行なえる.
(mapc 関数 リスト リスト リスト...)
mapcは第三引数以降の要素を順番にとりだし,関数の引数として渡して関数を実行する.複数のリストの内どれか一つでも処理済みの要素がなくなれば,mapcは処理を終える.逆に言うと複数のリストの要素数は同じでなくてもよい.
> (mapc #'(lambda (l m n o)
             (format t "l + m + n + o = ~A~%" (+ (+ l m) (+ n o))))
           '(  1   2   3   4   5)
           '(  6   7   8   9   0)
           '( 10  10  10  10)
           '(100 100 100 100 100))
l + m + n + o = 117
l + m + n + o = 119
l + m + n + o = 121
l + m + n + o = 123
(1 2 3 4 5)
上の例では四つのリストをそれぞれ関数の引数l,m,n,oに代入し,format式を実行している.三つめのリストの要素数4で最小なので,4回反復されている.mapcの返り値は第二引数,すなわち一つ目のリストである.
matsu(C)
Since 2002
Mail to matsu