Island Life

< ピアノレッスン27回目 | 質問の成長 >

2011/12/17

範囲の指定

Schemeでは「ここからここまで」という範囲を指定するのに、 始点(始点自身は含まれる)と終点(終点自身は含まれない)のhalf-open intervalを 使うことが多い。SRFI:13(string library)やSRFI:43(vector library) などの代表的なSRFIがそうだし、Gaucheもそれに合わせてる。これを [始点,終点) と書くことにしよう。

この他に代表的な流儀としては、始点と終点をともに含まれるとするclosed interval、 それから始点と長さがある。それぞれ [始点,終点], {始点,長さ} としよう。

SchemeではSRFI:1のiotaが例外的に{始点,長さ}を取る。但し引数順としては長さの方が先で、始点は省略可能、省略時0という形。3番目の引数としてステップを取ることもできる。

Pythonのarray slicingは[始点,終点)、オプショナルでステップ(ストライド)。

Common Lispのloopマクロは、[始点,終点][始点,終点)のどちらも使える。前者はfrom〜toとかfrom〜downto、後者はfrom〜belowとfrom〜above。

Rubyはどれも使える。配列でx[a,b]とやれば{始点,長さ}, さらにrangeオペレータが2種類あって[始点,終点][始点,終点)に対応。

この3方式は容易に相互変換可能なので、どれを採用するかは好みの問題、一貫性があればいいや、と今までは思っていた。

ところが、とあるAPIを考えていて、2つのパラメータで範囲指定させ、かつ昇順と降順の区別も出来たらいいな、と考えてたら、この3方式に表現力の違いがあることに気づいた。正確には、インデックスのround trip (-1, -2 など負のインデックスで、終端からの距離を数える場合。例えばpythonでa[0:-1]とやれば最後の要素を除いた配列が得られる、など) を使おうとした場合、だけれど。

  • {始点,長さ} は「長さが負の場合に降順とする」というルールを入れればすぐ対応できる。インデックスのround tripも特に問題ない。但し、「最後から3番目の要素を起点に最初の要素までを降順で」のように取りたい時に、範囲の長さを陽に与える必要がある (昇順なら、「長さ省略時が最後の要素まで」としておけるのに)。対称でないのはちょっと気持ちわるい。
  • [始点,終点] では 始点>終点 の時に降順とすることをすぐに思いつくが、round tripがあると単純な大小比較ができない。始点と終点をそれぞれmodulo(範囲の大きさ)した後で大小比較すれば何とかなる。若干、直感的でない場合は出てくる。
  • [始点,終点) が困りもの。ある値から逆順に先頭までを入れたい時には、「ゼロより一つ前」を終点として指定する必要がある。これを-1にしてしまうと、「終端からひとつ手前」を指定するのと区別がつかない。言い換えれば、この方式では「一番最後の要素+1」と「一番最初の要素-1」を別々に表現する必要があって、その分表記にバリエーションが必要になるということだ。end省略時に「最後の要素+1を指す」とする場合は多いが、「省略された」というのは1種類の情報でしかないので、降順を表現するのに別の情報が必要になる。

Luaみたいにインデックスを1からスタートすることにすれば0を「最初の要素のひとつ前」とすることが出来る。この点では1-ベースのインデクシングに利点があるとも言えるなあ。

Tag: Programming

Post a comment

Name: