-ao- ramune blog

©2024 unio / GO2直営からふるラムネ
2019年09月24日

JavaScriptでプリミティブ型の配列をディープコピーする

プリミティブ型1次元配列のディープコピー

Array.from を使うと、文字列や数値などの プリミティブ型 配列をディープコピーできます。

Array.from(ES6のみ)
                
                    const targets = [1, 2, 3];
                    const copied = Array.from(targets);

                    // 元の配列の0番目を書き換える
                    targets[0] = "replaced";

                    // プリミティブ値なので、コピー先には影響しない
                    console.log(copied[0]);

                    > 1
                
            

プリミティブ型多次元配列のディープコピー

Array.fromは低コストで配列をコピーできますが、多次元配列だとうまく行きません。

Array.from(ES6のみ)
                
                    const targets = [
                                        [1, 2],
                                        [3, 4]
                                    ];
                    const copied = Array.from(targets);

                    // 2次元配列の値を書き換える
                    targets[0][0] = "replaced";

                    // 配列なのでArray.fromだと参照コピーされる
                    console.log(copied[0][0]);

                    > replaced
                
            

再帰関数か、JSON.stringify/parseを使うと、 プリミティブ型の多次元配列をディープコピーできます。

プリミティブ型多次元配列のディープコピー
                
                    // 再帰関数パターン
                    function doRecursiveFunction(matrix) {
                        return matrix.map(row => {
                            if (Array.isArray(row)) {
                                return doRecursiveFunction(row);
                            }
                            return row;
                        });
                    }

                    // JSON.stringify/parseパターン
                    function doJSON(matrix) {
                        return JSON.parse(JSON.stringify(matrix));
                    }
                
            

JSON.stringify/parseパターンは、配列をJSON文字列に変換して再度配列に戻すパターンです。 非常にシンプルで分かりやすいのですが、処理速度は再帰関数に比べると数倍遅いです。

処理速度比較
                
                    const targets = [];
                    for (let i = 0; i < 100; i++) {
                        targets.push([1, 2, 3, 4, 5]);
                    }

                    let start = Date.now();
                    for (let i = 0; i < 100000; i++) {
                        doRecursiveFunction(targets);
                    }
                    console.log("doRecursiveFunction: " + (Date.now() - start) + " msec");

                    start = Date.now();
                    for (let i = 0; i < 100000; i++) {
                        doJSON(targets);
                    }
                    console.log("doJSON: " + (Date.now() - start) + " msec");

                    > doRecursiveFunction: 605 msec
                    > doJSON: 2678 msec
                
            

一般的に再帰関数も高コストなので、多次元配列の構造が判明している場合はArray.fromで組んだ方が高速に動作します。

2次元配列をArray.fromでディープコピー
                
                    // Array.fromパターン
                    function doArrayFrom(matrix) {
                        return matrix.map(row => Array.from(row));
                    }

                    const targets = [];
                    for (let i = 0; i < 100; i++) {
                        targets.push([1, 2, 3, 4, 5]);
                    }

                    let start = Date.now();
                    for (let i = 0; i < 100000; i++) {
                        doArrayFrom(targets);
                    }
                    console.log("doArrayFrom: " + (Date.now() - start) + " msec");

                    > doArrayFrom: 248 msec