繰り返し処理のカウンタについて
これまで、私は、n回の繰り返しは、こんな風に書いてきた。(手続き型言語の場合)
for(int i=0; i<n; ++i){ ... }
SICPを読んでいて、あることに気づいた。SICPでは、カウンタをこんな風に使っている。(例:p.45より)
(define (expt b n) (expt-iter b n 1)) (define (expt-iter b counter product) (if (= counter 0) product (expt-iter b (- counter 1) (* b product))))
つまり、ダウンカウントループになっているのだ。手続き型に書くとこう。
for(int i=n; i>0; --i){ ... }
で、ちょっと考えてみたのだけど、ダウンカウントループの方が、いろいろと都合がよいことが多いような気がしてきた。たとえば、
- ループの冒頭で、何回ループを繰り返すのかが分かる。
- ループの最中に、後、何回ループを繰り返すのかがすぐに分かる。
- 関数の引数として与えられた変数をカウンタとして流用できる。
- ループの回数として、関数の返値を利用する場合、効率のよい書き方になる
3つめのはこういうことだ。
void proc(int counter){ while(counter-- > 0){ ... } }
4つ目のはこういうことだ。
for(int i=func(); i>0; --i){ ... }
これを、アップカウントで下のように書くと、関数を何回も読んでしまって効率が悪いので、変数を用意して関数の結果を取っておかなければいけない。(Delphiの場合はちょっと違うのだが、それはそれで、面倒なこともある。)
for(int i=0; i<func(); ++i){ ... }
-
-
- -
-
このループを書く際の変な習慣はどこから来たのかとググって見たのだが、
http://www.sun-inet.or.jp/~yaneurao/intensive/tips1.html <- Tips 27. ダウンカウントループについて
に指摘されているには、
で、アップカウントループを使うべきだと書いてあるらしい。
C言語の習慣なのかと思い、他の言語も調べてみた。
COBOL
001000 PERFORM WITH TEST BEFORE VARYING CT-W FROM 1 BY 1 001001 UNTIL CT-W > 100 001002* ... 001010 END-PERFORM.
BASIC
For GYO = 1 To 100 ... Next GYO
FORTRAN
do i=1,100 ... enddo
ALGOL 60
for q:=1 step s until n do A[q]:=B[q]
うーん。いまいちはっきりしない。
-
-
- -
-
アップカウントというのは、手続き型の発想では、自然な発想だったのかもしれない。確かに、数を1から数えるのは、日常的な発想で、手順を考える場合には、非常に自然だ。しかし、再帰という発想を元にすると、ダウンカウントの方が自然な発想になるようにも思う。
アップカウントとダウンカウントの違いは、そういう、根本的な発想の源泉の違いが影響を与えているかもしれないといったら穿ちすぎか?