scripcryptor
@scripcryptor

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

javascriptで多次元配列を動的に生成したい

Q&A

Closed

解決したいこと

  1. 同的に多次元配列を生成したい
    var inputSubs = ['company',4,'dept',3,'member',11,'name'];
    var value = '田中太郎';
    var multiArr = [];
    set_dynamic_array(inputSubs,value,multiArr);

var inputSubs = ['company',5,'dept',2,'member',32,'name'];
var value = '高橋大介';
set_dynamic_array(inputSubs,multiArr,value);
......
以下の配列を生成したい
multiArr['company'][4]['dept'][3]['member'][11]['name'] の値は '田中太郎';
multiArr['company'][5]['dept'][2]['member'][32]['name'] の値は '高橋大介';

  1. 同的に対次元配列の値を取得したい
    var outputSubs = ['company',4,'dept',3,'member',11,'name'];
    var variable = get_dynamic_array(outputSubs,multiArr);
    ......
    以下の通りに値が取得したい
    variable の値は '田中太郎';

Javascriptには参照渡しが無いので、同的に多次元配列を生成する方法が思いつきません。どなたか良い方法があれば教えていただけませんでしょうか?よろしくお願いいたします。

0

7Answer

そうですよね... そう書いてあるんですけどどういう意味か・・・ もう少し調べて勉強します。

reduce メソッドは第1引数に渡された関数を何度も呼んで、配列から1つの値を作り出すメソッドです。

keys.reduce((acc, key) => acc[key] ||= [], array);

を for ループで書き直すと

let func = (acc, key) => acc[key] ||= [];
let result = array;

for (let i = 0; i < keys.length; i++) {
    result = func(result, keys[i]);
}

return result;

となります。 func を関数ではなく直に書くようにすると

let result = array;

for (let i = 0; i < keys.length; i++) {
    result = (result[keys[i]] ||= []);
}

return result;

ですね。ここまで来ればお書きになった for ループのコードとの類似点が見えると思います。

2Like

Comments

  1. @scripcryptor

    Questioner

    丁寧にご説明いただきありがとうございます。
    非常によく分かりました。
    複雑な事を一行で書けるんですね。
    reduceの使い方をいろいろ見て勉強します。

javascriptでfunctionに送った変数は参照渡しになって、繋がったまま??と思って試したところ、stringやnumberは別スコープ、arrayやobjectは参照渡しで繋がったままになっているんですね。

参照型変数は参照値を保持して、代入や関数呼び出しでは参照値が渡されます。
変数の値である「参照を値渡し」するのと、変数領域への参照を渡して変数を共有する「参照渡し」はまったく別の概念なので調べてみるといいですよ。

2Like

Comments

  1. @scripcryptor

    Questioner

    アドバイスをありがとうございます。
    参照型変数というのですね。「参照値を渡す」は値を渡す事で、「参照渡し」はポインターを渡すようなイメージなのですが・・・ 勉強してみます。
  2. JavaScriptは値渡し(変数の値を渡すこと、参照も値)しかできない言語で、参照渡し(変数領域への参照を渡すこと、変数共有、別名変数)ができない言語と言われてます。
    情報処理試験に出題されるようなプログラムをJavaScriptでは実装できない、ということになります。実装できるのは C++, C#, PHP, VisualBasic, FORTRAN, etc.
    参考: https://kanauka.com/kakomon/ap/h25h/020.html
  3. @scripcryptor

    Questioner

    ありがとうございます。

    「JavaScriptは値渡ししかできない言語」と思ったので今回の質問だったわけですが、スコープの違うファンクションへ変数(arrayかobject)を渡した時、そのファンクション内でその受け取った引数に変更を加えると、元の変数にも変更が反映される・・・
    この動作を参照渡しと捉えないとすると、どう捉えれば良いのか・・・

    「情報処理試験に出題されるようなプログラムをJavaScriptでは実装できない」のあたりを勉強してみます。
  4. オブジェクトへの参照を渡してオブジェクトを共有するので、変更の影響をうけます。
    「変数の値(参照型変数の値はオブジェクトへの参照)を渡すこと」は情報処理用語的には「値渡し」です。情報処理用語の「参照渡し」は変数領域への参照を渡すこと(変数共有)で、オブジェクトへの参照(オブジェクト共有)とは別物であることが理解できるといいのですが・・・
    別記事へのコメントも参考にしてみてください。
    https://qiita.com/osakanaaaa/items/a79fdbb7afbfaacbdc6e#comment-23bbdc99c84348654413
  5. @scripcryptor

    Questioner

    ありがとうございます。
    調べてみて違いは分かりました。以下のページに非常に詳しく説明してありました。https://magazine.rubyist.net/articles/0032/0032-CallByValueAndCallByReference.html

    内部構造的に全く違う動きをする事は理解できました。
    また、プログラマとしては参照でメモリ共有している、という点で表面上の動作は同じだという事も理解できました。
    うっかり変数共有してしまう事故を防ぐための理解が正しく出来たかと思います。

    勉強になりました。重ねてありがとうございました。
  6. 理解していただけてよかったです。
    参照渡しかどうかは、呼び出し先で変数自身に再代入したときに参照元に影響するかどうかが変わります。

    function sub(x) {
        x.value = 123;  // 変数xからオブジェクトへの参照を取り出し、オブジェクト内のvalueに値を代入する
        x = { value: 999 };  // 変数xに再代入、値渡しなので呼び出し元には影響しない
    }
    
    a = { value: 0 };
    sub(a);
    console.log(a.value);
    
  7. @scripcryptor

    Questioner

    確かにそういうところで差が出ますね。
    x は配列 a のエイリアスではなく、あくまで a の参照「値」だから、x 自体に値を入れようとすると、その参照値が上書きされる、つまりその時点で a とは関係なくなる、という事ですね。

Javascriptには参照渡しが無いので、同的に多次元配列を生成する方法が思いつきません。

引数で受け取った配列に要素を追加することは普通にできますよ。

適当に書いてみました。真面目にやるなら配列に元からあるプロパティ名が inputSubs に含まれるケースのハンドリングなどもするべきですがサボってます。

function set_dynamic_array(keyPath, value, array) {
    const keys = [...keyPath];
    const lastKey = keys.pop();
    const lastItem = keys.reduce((acc, key) => acc[key] ||= [], array);
    lastItem[lastKey] = value;
}

function get_dynamic_array(keyPath, array) {
    return keyPath.reduce((acc, key) => 
        typeof acc[key] === "undefined" ? [] : acc[key],
    array);
}
1Like

以下の行の最後の ', array' のところは何をしている部分なのでしょうか?

acc の初期値です。詳しくは Array.reduce のドキュメントを読んでください。

お書きになったコードでも全然いいと思いますよ。ただ const lastKey = keyPath.pop(); だと呼び出し元の inputSubs 配列も書き換わってしまうので、 keyPath をコピーしてからにするか、以下のようにインデックスを工夫してもいいでしょう。

function set_dynamic_array(keyPath, value, array) {
    for (var i = 0, last = keyPath.length - 1; i < last; i++) {
        array[keyPath[i]] ||= [];
        array = array[keyPath[i]];
    }
    array[keyPath[last]] = value;
}
1Like

出来ていますね。正しく動作している事を確認しました。
ありがとうございます。

ところで私の知識では今ひとつソースを読み解く事が出来ないのですが、以下の行の最後の ', array' のところは何をしている部分なのでしょうか?
const lastItem = keys.reduce((acc, key) => acc[key] ||= [], array);

非常に稚拙ながら私なりに set_dynamic_array() を書いてみましたところ、これも正しく動作しました。普通に出来ますね。参照渡しじゃないから出来ないと決めつけてました。

function set_dynamic_array(keyPath, value, array) {
	const lastKey = keyPath.pop();
	for (var i = 0, ci = keyPath.length; i < ci; i++) {
		array[keyPath[i]] ||= [];
		array = array[keyPath[i]];
	}
	array[lastKey] = value;
}

function get_dynamic_array(keyPath, array) {
	const lastKey = keyPath.pop();
	for (var i = 0, ci = keyPath.length; i < ci; i++) {
		array = array[keyPath[i]];
	}
	return array[lastKey];
}

var inputSubs = ['company', 4, 'dept', 3, 'member', 11, 'name'];
var value = '田中太郎';
var multiArr = [];
set_dynamic_array(inputSubs, value, multiArr);
console.log(multiArr);

var inputSubs = ['company', 5, 'dept', 2, 'member', 32, 'name'];
var value = '高橋大介';
set_dynamic_array(inputSubs, value, multiArr);
console.log(multiArr);

var outputSubs = ['company', 4, 'dept', 3, 'member', 11, 'name'];
var variable = get_dynamic_array(outputSubs, multiArr);
console.log(variable);

これをもってjavascriptの代入は参照渡しであるとは言えないのでしょうけど、ここでの動作では参照渡ししているように見えます。

0Like

acc の初期値です。

ありがとうございます。
そうですよね... そう書いてあるんですけどどういう意味か・・・ もう少し調べて勉強します。

const lastKey = keyPath.pop(); だと呼び出し元の inputSubs 配列も書き換わってしまう

ファンクションに送った値をファンクション内で変更してもスコープが違うから・・・と思ったのですが、確かにおっしゃる通り inputSubs配列も書き変わってしまいます。という事は、javascriptでfunctionに送った変数は参照渡しになって、繋がったまま??と思って試したところ、stringやnumberは別スコープ、arrayやobjectは参照渡しで繋がったままになっているんですね。

誰かの役に立てばという事で、実験したソースを載せておきます。

function my_name_is(mn) {
	mn = 'ジョンソン';
}

function my_name_arr(mna) {
	mna[0] = '鈴木';
}

(() => {
	/* Stringを送る */
	const myname = 'スミス';
	my_name_is(myname);
	console.log(myname); // 'スミス' 変更されない
	/* Arrayを送る */
	const mynamearr = [];
	mynamearr[0] = '佐藤';
	my_name_arr(mynamearr);
	console.log(mynamearr); // '['鈴木']' 変更される
})();
0Like

Your answer might help someone💌