0から始まるか1から始まるか。

ゼロでなく1ではダメなのでしょうか。

JavascriptやPHPの配列や関数などで 配列の最初の要素がary[0]に… - 人力検索はてな

なぜそうなっているか‥それは実装上の都合であったり歴史的理由だったりする。

配列の先頭要素が「0番目」であることは気持ち悪いか…「N番目」という言葉を考察してみる - http://rubikitch.com/に移転しました

あんまり面白くない話題だが、書ける記事がないのでちょっと考えてみる。common lispの基本はリストなだけあって、リストでの順番を記述する方法が数多くある。さて、順番を記述するときは、プログラムでは0から始まるのか、1から始まるのかで揉めることがある。しかし、lispでは大概の場合それを気にしなくて良い。なぜなら、大抵、最初か最初から2つ3つくらいを指定できれば良く、それを指定するにはcarやcadrといった関数を使うからだ。

(setq ls '(1 2 3 4 5)) ;->(1 2 3 4 5)

(car ls) ;->1
(cadr ls) ;->2
(caddr ls) ;->3

(cdr ls) ;->(2 3 4 5)

carはリストの先頭(consの前者)を返し、cdrはリストの後方(consの後者)を返す。所望の要素を取り出すのはこれの繰り返しをすればよい。こういう考え方なので、0も1もない。


これが気にくわないなら、次にいくつかの他の関数を挙げる。

(first ls) ;->1
(second ls) ;->2
(third ls) ;->3

(nth 1 ls) ;->2
(nth 0 ls) ;->1

carと同じようなものにfirstがある。まあ、1番目ということで、当然1番目を返すわけだ。こんな感じでtenthまである。それ以上の時はnthで番号を指定する……のだが、この番号はなぜか0から始まる。実は、数値で指定するときは0が基点だったりする。ちょっとひねくれているね。他に、nthと同じような関数にeltというものもある。

(elt ls 1) ;->2
(elt ls 0) ;->1

また、リストの最後を指定する関数もある。これは最後からn個の要素をリストで返すので、(実際は違うけど)1を基点にしているように見える。

(last ls 1) ;->(5)
(last ls 0) ;->nil

なんでこうなってるか

まあ、ごちゃごちゃしていて、0からか1からかどころではない。そんなつまらないことで悩むなら、考え方を変えた方がよいのだ。結局、carやcdrの考え方が基本である。先頭と後方だ。それを順番で数えようとすると、1番目、2番目となるから、直観的にはfirst、second……になる。


しかし、リストを双六に喩えると、0から数える方が数えやすい。リストの先頭がスタートで、1といえば1進んだマスである。とすると、最初のマスは0だ。マスの数と、進んだ数は1ずつずれることになる。
また、最後を数えるときは、(last ls 1)が最後を返すようにみえるけど、本当は違う。listの最後はnilで終わるのだ。ここにnil以外のものを入れると普通のリストとしては扱われなくなる。

(setf (cdr (last ls 1)) 6) ;->6
(last ls 0) ;->6
ls ;->(1 2 3 4 5 . 6)

リストの正体は、コンスのつながったものだ。consというのは、詐欺師の複数形……じゃなくて、リストに加えるという意味で、2つの要素を指し示す道標みたいなものである。リストは、一方が要素、一方が次のコンスを示すコンスの連続でできている。そして、最後はnilで終わりにする。まあ、最後がnilじゃなくて、他の要素でも大した問題はないし、自分自身を返すやつも有ったりするけど、まあそれらは特殊な部類だ。

(setf (cdr (last ls 1)) (cons 6 nil))
ls
(1 2 3 4 5 6)

どうも、lastの返り値はsetfできないようなので、最後から1つ戻った要素からのリスト(まあ、最後のコンスということになる)の後方にコンスを加えてやる。そうすると、もとのリストに普通に要素を加えたことになる。もちろん、同じようにリストを繋げても良い。

(setf (cdr (last ls 1)) (list 7 8 9 10)) ;->(7 8 9 10)
ls ;->(1 2 3 4 5 6 7 8 9 10)

まあ、これだけならappendで良いけど。双六のイメージが有れば、分岐させたり、途中に要素を追加したり、ループさせたりが簡単だということも理解しやすいと思う。コンス単位で考えると、それは二分木構造っていうらしいけど。


強引にまとめると、0からか1からかという問題は、最初=1番目っていうのと、n個進んだところ、っていうのをごっちゃにしなければ、両立できる考えなのである。


(リストと配列、シーケンスは色々違う考え方が必要だけどね)