はじめに
前回こんな記事を書きました。
- 最短コードでrange(start,stop,step)を実装する。
- 出力はリスト・イテレータを問わない
- 負の値は考慮しない(start, stop, step, 出力する連番すべて0より大きいとする)
実装でかなりいろんなアイデアを出せたんですが、どれもPythonの負のステップを実現できていません。
Pythonのrange関数はかなり便利で、特にstepに-1を入れると連番リストを逆順にして返してくれるという機能があります(正確にはリストではないですが)。
前回の記事でもこの点が実装できてないことを指摘され、自分でもそれが痛いなぁと感じていました。それからというもの、ことあるごとにJavaScriptでのPython range関数の実装を考えるようになっていました。しかし今回、ついに?ワンライナーでのそれっぽい実装を思いついたので、記しておきます。
本題
まずコードから。
var f=(a,b,c)=>(b-=a+1)&&(([d,e])=>[...Array((b-b%c)/e+1)].map((_,i)=>d+i*c))(c>0?[a,c]:[b+a-b%c,-c])
ワンライナーを意識して書きたかったのですが、かなり長くなりました。101文字で、減算代入を使っているので関数の形でしか使えません。前回の最短、ジェネレータ記法が45字だったことを考えると、随分長いですね。
ロジックを読みやすいように、書き下してみます。
function range(start, stop, step) {
stop -= start + 1;
const inner = ([startValue, positiveStep]) => {
const length = (stop - (stop % step)) / positiveStep + 1;
return [...Array(length)].map((_, i) => startValue + i * step);
};
if (step > 0) {
return inner([start, step]);
} else {
return inner([stop + start - (stop % step), -step]);
}
}
結果
あまり本腰を入れてテストしたわけではないですが、どうやら動いてそうですね。
解説
inner関数が機能の中心です。等差数列の式初項 + 公差 * index
をここで計算しています。
しかし難しいのはそれ以外の場所で、
- 末項がstopよりも手前のときの、末項の値
- stepが負の時の、配列の長さ
- stepが負の時の、初項の値
などに場合分けや剰余算を使って対処しています。
ショートハンド版ですが、減算代入の組み込みに苦労し、文字数をかなり使っています。その上実は、stop == start + 1
のときだけfalse
が返るというバグもそこで組み込んでしまっています。w 全体的にテクニックを盛り込めず、長くて率直な書き方になってしまったのでそこが要改善ですね。
しかし今回は、range関数の複雑な処理をそれっぽく実装するというのが趣旨だったので、短縮アイデアには前回ほど凝っていません。
短縮アイデア、別の実装、バグなどなど、思いついたらぜひ教えてください。
以上です。