質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

3156閲覧

GASで多次元配列の編集が上手くいかない件

退会済みユーザー

退会済みユーザー

総合スコア0

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2018/12/07 03:57

編集2018/12/07 04:00

前提・実現したいこと

Google Apps Scriptで、多次元配列をarray[i][j]で編集したい。
一方、他のプロジェクトファイルで同等のコードを実行した場合は上手くいく。(本当に謎)

発生している問題・エラーメッセージ

通常なら、簡易モデルですが[[0,0,0,0,0][0,0,1,0,0][0,0,0,0,0]]となるはずが、[[0,0,1,0,0][0,0,1,0,0][0,0,1,0,0]]となってしまいます。

該当のソースコード

GoogleAppsScript

1function main(n) { 2 //----------------------------------------------- 3 4 n=11; 5 //とりあえずnの値を代入 6 7 var maze = []; 8 var array = []; 9 10 for(var j = 0; j < n; j++) { 11 array.push(0); 12 } 13 //arrayに[0,0,..](0が11個)を代入 14 15 for(var i = 0; i < n; i++) { 16 maze.push(array); 17 } 18 //mazeに[array,array,...](arrayが11個)を代入 19 20 21 //----------------------------------------------- 22 23 var start = [Math.floor(Math.random() * (n - 1) / 2) * 2 + 1, Math.floor(Math.random() * (n - 1) / 2) * 2 + 1]; 24 //startに[1から9までの奇数,1から9までの奇数]を代入 25 26 Logger.log(start[0]+"+"+start[1]); 27 28 maze[start[0]][start[1]] = 1; 29 //2次元配列mazeを書き換え 30 31 Logger.log(maze); 32} 33

試したこと

書き換えをrewrite関数(自作)に置き換えても結局ダメだった

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dice142

2018/12/07 04:03

どういう出力を期待していて、現状どういう出力になっているのか追記してください。
dice142

2018/12/07 04:03

失礼しました。ありましたね。
guest

回答3

0

ベストアンサー

既に良い感じの回答が出てますが、
もう少し深掘りして原理の方を解説します。

JavaScript

1console.log([1, 2, 3] === [1, 2, 3]); // false 2 3var a = [1, 2, 3]; 4var b = a; 5console.log(a === b); // true

不思議ですね、どうしてこういう動きするか説明出来ますか?
多分説明出来ないと思います。


JavaScriptの世界では、{name: "hoge"}というオブジェクトや、[1, 2, 3]という配列を宣言した時、初めてメモリ空間上にオブジェクト/配列が生成されます。
変数や引数として使う時は全て「メモリ空間のアドレス番地」でやりとりされます(参照やポインタ等と表現されます)
変数に代入された時、目には見えませんしとりだせませんが、オブジェクト/配列の実体ではなく、アドレス番地という数値が代入されていると考えてください。

[1, 2, 3] === [1, 2, 3]の結果がfalseになるのは、比較演算子でオブジェクトや配列を比較した場合、配列そのものではなく、「アドレス番地」での比較になります。
1行内に配列宣言が2個ありますので、メモリ空間上に配列2個作られ、それぞれのアドレス番地が帰って来ます。
各々のアドレス番地は当然異なる為、比較結果もfalseになるというカラクリになっています。

これがどう質問文に影響してくるかを見ていきましょう。

JavaScript

1 var n = 11; 2 var array = []; 3 4 for(var j = 0; j < n; j++) { 5 array.push(0); 6 }

このvar array = [];で配列を1個生成しました。
for文で配列にぽこぽこ0を挿入していき0を11個含む配列に成長しました。

JavaScript

1 for(var i = 0; i < n; i++) { 2 maze.push(array); 3 }

問題の箇所がこれです。
maze.push(array)としていますが、arrayには配列のアドレス番地が入ってます。
つまり、11回繰り返した結果mazeは[arrayのアドレス番地, arrayのアドレス番地, arrayのアドレス番地, ...]という配列になります。

理想はメモリ空間上にmazeとarrayのコピー11個、計12個の配列が存在してmazeの中身は[array1, array2, array3 ...]という風になって欲しいはずですが、
実際にはmazeとarrayの計2個の配列しか存在していません。
そりゃarrayの値を1個弄ったら全部変化するよね……という訳で、そのコードは欠陥となるわけです。

配列は基本的に宣言したタイミングで作られるので、
素直に解決するなら多重ループに変更してこういったコードになるでしょう。

JavaScript

1function main (n) { 2 n = 11; 3 var maze = []; 4 for (var i = 0; i < n; i++) { 5 // ループの中で新しい配列を作って育てる 6 var array = []; 7 for (var j = 0; j < n; j++) { 8 array.push(0); 9 } 10 maze.push(array); 11 } 12}

ですが、ループやif文のネストって理解が難しくなるのでできれば避けたいですよね。
そういった時に、メモリ空間上に配列の複製を作ってしまう手法があり、
これをシャローコピーやディープコピーと呼びます。
詳しい記事を見つけたので、具体的な方法はこちらを読んでみてください。

JavaScript の配列のシャローコピーに使えるメソッド3つ(+ 1つ)とその使い分け - おかかウェブ

回答文に上がっている解決策はこのシャローコピーやディープコピーの手法です。
(この手法はどちらもちゃんとGASで使える事を確認しています)

  • JSON.parse(JSON.stringify(...)): 簡易的なディープコピー
  • array.slice(): スマートなシャローコピー

【おまけ】【修正版】関数作ってワンライナーで頑張る

一撃で行けると思ったのですが、どうもGASはJavaScript1.6ベースでできており、
ECMAScript6の便利機能は全滅、5も動作が怪しいので、注意してください。

参考サイト

そもそも配列の要素を一気につくるイディオムがES6に頼る事になるので、
それに頼れないとなると一気に微妙化しますが、こんな感じで配列作る関数を用意して動作させることになります。
下記のコードは調査済みで動作します…が、array.slice()の方が明らかに筋が良いですね。参考までにどうぞ。

JavaScript

1function myFunction() { 2 var n = 11; 3 var array = function (num) { 4 var arr = []; 5 for (var i = 0; i < num; i++) { 6 arr.push(0); 7 } 8 return arr; 9 } 10 var maze = array(n).map(function () { return array(n); }); 11 Logger.log(maze); 12} 13 14// [18-12-07 17:36:31:549 JST] [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]

投稿2018/12/07 05:03

編集2018/12/07 08:42
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/12/07 07:32

回答ありがとうございました!なぜ自分の問題が発生するのかがよくわかり、それへの対処法も豊富だったので、この回答をベストアンサーにさせて頂きました (^^)
papinianus

2018/12/07 07:37 編集

おまけのところはjsとしては妥当でも、google app scriptではArray.prototype.fillもなければ、アロー関数の構文も使えないので実行できないはず(試した結果のBAなのでしょうか?)
miyabi-sun

2018/12/07 07:40

おおっと、まじですか…GAS見てなかったので確認します。
退会済みユーザー

退会済みユーザー

2018/12/07 07:47

確かにおまけのところはGASでは無理そうですね...確認不足でしたごめんなさい。
miyabi-sun

2018/12/07 08:20

苦戦しましたが見つけてきました。 https://developers.google.com/apps-script/guides/services/#basic_javascript_features > Apps Script is based on JavaScript 1.6, plus a few features from 1.7 and 1.8. 和訳すると「Apps ScriptはJavaScript1.6に対応、更に一部1.7や1.8にも対応しています!」 で、JavaScript1.nがどう関わってくるかをWikipediaで調べたら、どうもやっとECMAScript5に準拠できたのがJavaScript1.9であり、それ未満である1.8以下というのは相当ヤバいですねこれは…… https://ja.wikipedia.org/wiki/JavaScript おまけどころか1行イディオムは全部アウトで、 `Logger.log(Object.getOwnPropertyNames(Array.prototype))` で持っているメソッド一覧を確認したら [constructor, toString, toLocaleString, toSource, join, reverse, sort, push, pop, shift, unshift, splice, concat, slice, indexOf, lastIndexOf, every, filter, forEach, map, some, reduce, reduceRight, length] とのことで、現行のJavaScriptからはかなり遅れているようです。回答を編集して書き直しておきます。 https://stackoverflow.com/questions/39317252/what-functions-do-arrays-in-google-apps-script-support
papinianus

2018/12/07 08:23

すごい。この調査力。「Rhinoだ(推測)」みたいな記事まで読んであきらめてました。
guest

0

JavaScript

1 for(var i = 0; i < n; i++) { 2 maze.push(array); 3 }

このarrayは同じオブジェクトなので、mazeには同じオブジェクトをn個入れていることになります。
どれか書き換えると他の箇所も同様に変わっているので、期待したとおりにならないかと思います。

arrayをそれぞれ別なオブジェクトとして入れるようにすれば良いので、
都度数値を生成してもいいでしょうし、
JSON.parse(JSON.stringify(...))で文字列化→オブジェクト化してもできると思います。

投稿2018/12/07 04:07

dice142

総合スコア5158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/12/07 04:31

上記のfor構文で、maze=[array,array...]となっているので、maze[0]=array2とした時、mazeが[array2,array...]ではなく、[array2,array2...]となってしまう、という解釈でよろしいですか?
dice142

2018/12/07 04:34

その解釈で合ってるように思います。
退会済みユーザー

退会済みユーザー

2018/12/07 04:51

function test() { var maze = [] var a = 2; var b = 3; var n = 5; for(var i = 0; i < n; i++) { maze.push([0,0,0,0,0]);//n=5だから0が5個 } maze[a][b] = 1; //2次元配列を編集 Logger.log(maze); } と改めたらうまくいきました。しかし、このコードだとmaze.push()の中の[0,0,0]の数を、nの値に応じて手動で変更しなければいけません。なにか解決策はありますか?
dice142

2018/12/07 04:56

回答にも書いてますが、二重ループで都度配列を生成してもいいでしょうし、 JSON.parse(JSON.stringify(xxx))でオブジェクトを文字列化して再度オブジェクト化することで別オブジェクトとすることも可能です。 ただ、配列であればpapinianus様のsliceが一番楽だと思います。
退会済みユーザー

退会済みユーザー

2018/12/07 05:13

「二重ループで都度配列を作成」をもう少し詳しく教えていただけませんか?
dice142

2018/12/07 05:47

var array = []; で一つの配列を生成していて、[]が新しい配列オブジェクトになります。 つまりこの部分をfor文の中で行えば都度オブジェクトが生成されることになります。 外側のループは配列の中に入れる配列の数(mazeの中のarrayの数) 内側のループは中の配列に入れる要素数(各arrayの要素数) と二重ループで行えば都度生成された配列をmazeに入れることができます。
papinianus

2018/12/07 06:53 編集

横から失礼。0などの初期値でフィルされた配列が欲しいのであれば、↓こういう関数を作るしかないです。(gasはjsの仕様が古いので) function array_fill(n, defVal) { var ret = []; for(var i = 0; i < n; i++) { ret.push(defVal); } return ret; } 使うときは、↓こう maze.push(array_fill(n, 0));
退会済みユーザー

退会済みユーザー

2018/12/07 07:34

dice142さん、回答と丁寧な対応ありがとうございました!
guest

0

同じ配列を参照してます。

安直ですが、↓したらどうでしょう?

javascript

1maze.push(array.slice());

投稿2018/12/07 04:07

papinianus

総合スコア12705

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問