nekoTheShadow’s diary

IT業界の片隅でひっそり生きるシステムエンジニアです(´・ω・`)

javascriptで配列を分割、そしてsliceの使い方の注意点。

今日の作業中(というか勉強中)、配列を任意の数の要素に分割したいとなりました。ことばだとわかりづらいですね。たとえばこんな感じ。

[1,2,3,4,5,6,7,8,9].divide(4) => [[1,2,3,4],[5,6,7,8],[9]]
[1,2,3,4,5,6,7,8,9].divide(3) => [[1,2,3],[4,5,6],[7,8,9]]

rubyでいえばeach_sliceですね(るりま)。今回はこれをjavascriptで実装しました。といってもそれほど難しいことはなく、さらっとかけそうです。

Array.prototype.divide = function(n){
    var ary = this;
    var idx = 0;
    var results = [];
    var length = ary.length;

    while (idx + n < length){
        var result = ary.slice(idx,idx+n)
        results.push(result);
        idx = idx + n
    }

    var rest = ary.slice(idx,length+1)
    results.push(rest)
    return results;
}

ary = [1,2,3,4,5,6,7,8,9]

dividedAry1 = ary.divide(4);
console.log(dividedAry1);
// => [ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9 ] ]

dividedAry2 = ary.divide(3);
console.log(dividedAry2);
// => [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ]

できました。需要はあるかどうかわかりませんが。

ちなみに今回コーディング中に引っかかったのはsliceの使い方。[0,1,2,3,4].slice(0,3)としたとき、てっきり[0,1,2,3]が戻り値になると思っていたのです。が、これは間違い。実際は[0,1,2]が戻り値です。これが最初気づかず、なかなか苦労させられました。

どうやらsliceはふたつ目の引数endを指定したとき、endの直前までを取り出すようなのです。事実MDNにも次のように書かれています。

slice は end 自体は含めず、その直前まで取り出します。
slice(1, 4) は 2 番目の要素から 4 番目の要素まで (添字が 1, 2, 3 の要素) を取り出します。
end は負の添字を使って、配列の終わりからのオフセットを表すことができます。
slice(2, -1) は配列の 3 番目の要素から、最後から 2 番目の要素まで取り出します。
end が省略された場合、slice は配列の最後まで取り出します。

これは知らなかった。sliceは比較的使用頻度の高いメソッドのはずですが…… 如何に「なんとなく」使っていたのかがよくわかりますね。

もうひとつ注意したいのはslice(0)。これはレシーバのオブジェクトをコピーします。Rubyでいうところのdupですね(るりま)。

ary = [1,2,3];
fake = ary; // 参照渡し
copy = ary.slice(0);

ary[0] = 2;
console.log(ary);  //=> [2,2,3]
console.log(fake); //=> [2,2,3]
console.log(copy); //=> [1,2,3]

それにしてもなぜこんな書き方をするのか? 不思議に思ってMDNを読んでみたところ―――ふむふむ、sliceは第2引数を指定しなかった場合、オブジェクトの終わりまで取り出すのか。つまりslice(0)this[0]からその終わりまで取り出していることになり、結果としてあたかもレシーバがコピーされたかのように見えるわけですね。

またひとつ賢くなってしまった。こういう細かな仕様を調べていくのもプログラミングの醍醐味ですよね。