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

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

新規登録して質問してみよう
ただいま回答率
85.50%
for

for文は、様々なプログラミング言語で使われている制御構造です。for文に定義している条件から外れるまで、for文内の命令文を繰り返し実行します。

JavaScript

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

ハッシュ

ハッシュは、高速にデータ検索を行うアルゴリズムのことです。

Q&A

解決済

1回答

3590閲覧

【JavaScript】ネストされた連想配列の中で、マッチするプロパティにたどり着くまでの、プロパティの一覧を取得したいです

kde

総合スコア29

for

for文は、様々なプログラミング言語で使われている制御構造です。for文に定義している条件から外れるまで、for文内の命令文を繰り返し実行します。

JavaScript

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

ハッシュ

ハッシュは、高速にデータ検索を行うアルゴリズムのことです。

0グッド

0クリップ

投稿2017/12/09 05:31

編集2017/12/09 06:42

###前提・実現したいこと
JavaScriptで、ネストされた連想配列の中でマッチする任意のプロパティ(ID)にたどり着くまでの親プロパティ(ID)を配列として取得したいです。

下記のデータの中から、例えば"100106"と引数に指定したら、["1", "10", "1001", "100106"]と返ってくる関数を作れればと考えています。
"15"であれば["1", "15"]
"2101"であれば["2", "21", "2101"]
といった具合です。

マッチしなかったらfalseやnullが返ってきてほしいです。

for in文などを組み合わせて試みたものの、上手くいっていません。。
ご教示いただけますと幸いです。

javascript

1var obj = { 2 "1": { "name": '', "child": { 3 "10": { "name": '', "child": { 4 "1001": { "name": '', "child": { 5 "100101": { "name": '', "child": {}}, 6 "100106": { "name": '', "child": {}}, 7 }}, 8 "1002": { "name": '', "child": { 9 "100201": { "name": '', "child": {}} 10 }} 11 }}, 12 "15": { "name": '', "child": { 13 "2001": { "name": '', "child": {}} 14 }}, 15 "20": { "name": '', "child": { 16 "2006": { "name": '', "child": {}} 17 }} 18 }}, 19 "2": { "name": '', "child": { 20 "21": { "name": '', "child": { 21 "2101": { "name": '', "child": { 22 "10210101": { "name": '', "child": {}} 23 }}, 24 "2102": { "name": '', "child": {}} 25 }} 26 }} 27};

データについては、下記の形を基本としており、childプロパティの中にネストされています。

"id(数値)": { name: '', child: {/* 子がいれば入る */} }

###補足情報
ES5でもES2015でも問題ありません。

試したコード

下記が途中まで試してみたコードです。
とりあえずどうにか取れるのを確認できればと思って実装しました。
第一階層の直下配下に属するものであれば問題ないのですが、その他だと上手く取れていないようです。

<考えていたロジック>

  • パースしたidを配列に入れていく
  • パースしていく中で指定のidとマッチしたらループ終了
  • childプロパティが空だったら(= 指定のIDに当たらなければ)配列を空にして次のループを実行する

<分からないこと>

  • ロジックが誤っていると思うが、どういったロジックが良いかわからない。考え中。
  • ループの途中で何かしら処理を加える必要があると思うが、ロジックが定まっていないためどのような処理を加えればいいか分からない

javascript

1console.log(getPropPath('1001')); // => ["1", "10", "1001"] OK 2console.log(getPropPath('100106')); // => ["100106"] NG 3console.log(getPropPath('100201')); // => ["1002", "100201"] NG 4console.log(getPropPath('1003')); // => ["1003"] NG 5console.log(getPropPath('10210101')); // => ["2", "21", "2101", "10210101"] OK 6 7function getPropPath(id) { 8 var result = []; 9 10 HOGE: 11 for (var key in obj) { 12 result.push(key); 13 if (key === id) { break HOGE; } 14 if (Object.keys(obj[key].child).length === 0) { 15 result.length = 0; 16 continue; 17 } 18 var obj1 = obj[key].child; 19 for (var key in obj1) { 20 result.push(key); 21 if (key === id) { break HOGE; } 22 if (Object.keys(obj1[key].child).length === 0) { 23 result.length = 0; 24 continue; 25 } 26 var obj2 = obj1[key].child; 27 for (var key in obj2) { 28 result.push(key); 29 if (key === id) { break HOGE; } 30 if (Object.keys(obj2[key].child).length === 0) { 31 result.length = 0; 32 continue; 33 } 34 var obj3 = obj2[key].child; 35 for (var key in obj3) { 36 result.push(key); 37 if (key === id) { break HOGE; } 38 if (Object.keys(obj3[key].child).length === 0) { 39 result.length = 0; 40 continue; 41 } 42 var obj4 = obj3[key].child; 43 for (var key in obj4) { 44 result.push(key); 45 if (key === id) { break HOGE; } 46 if (Object.keys(obj4[key].child).length === 0) { 47 result.length = 0; 48 continue; 49 } 50 } 51 } 52 } 53 } 54 } 55 56 return result; 57}

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

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

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

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

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

kei344

2017/12/09 05:44

ご自身で試されたコードを質問文に追記し、「何」が「どのように」わからないのか、コードのどの部分で詰まっているのかなどを具体的に追記されたほうが回答が望めると思います。
kde

2017/12/09 06:44

コメントありがとうございます。試したコードや不明点を書いてみましたが、そもそもロジックが定まっていないのが一番よくないところに思えました。
guest

回答1

0

ベストアンサー

for in文などを組み合わせて試みたものの、上手くいっていません。。

プログラミングスキルでゴリ押しで解くことは可能です、
使うなら最も楽なのが再帰関数を用意することです。
例えばこんな感じ。

JavaScript

1var obj = { 2 "1": { "name": '', "child": { 3 "10": { "name": '', "child": { 4 "1001": { "name": '', "child": { 5 "100101": { "name": '', "child": {}}, 6 "100106": { "name": '', "child": {}}, 7 }}, 8 "1002": { "name": '', "child": { 9 "100201": { "name": '', "child": {}} 10 }} 11 }}, 12 "15": { "name": '', "child": { 13 "2001": { "name": '', "child": {}} 14 }}, 15 "20": { "name": '', "child": { 16 "2006": { "name": '', "child": {}} 17 }} 18 }}, 19 "2": { "name": '', "child": { 20 "21": { "name": '', "child": { 21 "2101": { "name": '', "child": { 22 "10210101": { "name": '', "child": {}} 23 }}, 24 "1002": { "name": '', "child": {}} 25 }} 26 }} 27}; 28var parse = (obj, path = []) => { 29 const keys = Object.keys(obj); 30 if (keys.length === 0) return []; // 既定部 31 return keys 32 .map(key => [[...path, key], ...parse(obj[key].child, [...path, key])]) 33 .reduce((arr, it) => [...arr, ...it], []); 34} 35var pathByKey = parse(obj).reduce((path, it) => { path[it[it.length - 1]] = it; return path}, {}); 36console.log(pathByKey);
{ '1': [ '1' ], '2': [ '2' ], '10': [ '1', '10' ], '15': [ '1', '15' ], '20': [ '1', '20' ], '21': [ '2', '21' ], '1001': [ '1', '10', '1001' ], '1002': [ '2', '21', '1002' ], '2001': [ '1', '15', '2001' ], '2006': [ '1', '20', '2006' ], '2101': [ '2', '21', '2101' ], '100101': [ '1', '10', '1001', '100101' ], '100106': [ '1', '10', '1001', '100106' ], '100201': [ '1', '10', '1002', '100201' ], '10210101': [ '2', '21', '2101', '10210101' ] }

15キーの配下に2001が入っていたりしますが、これは探索木ですか?
もし探索木のつもりなら要件を満たしていません。

結局全て開いてみる必要がありこれが正解であれば、
私の回答のようにに全て探索してインデックスを作っておくのが最善になるでしょう。

もし機密情報だったりして他人に理由を説明出来ないのであれば仕方ないですが、
データ部をもう少しマシな設計に出来ないか検討してみてください。


【追記】

マッチしなかったらfalseやnullが返ってきてほしいです。

上記のコードから進めます。

JavaScript

1var pathByKey = parse(obj).reduce((path, it) => { path[it[it.length - 1]] = it; return path}, {}); 2console.log(pathByKey['1001']); // [ '1', '10', '1001' ] 3console.log(pathByKey['100106']); // [ '1', '10', '1001', '100106' ] 4console.log(pathByKey['100201']); // [ '1', '10', '1002', '100201' ] 5console.log(pathByKey['1003']); // undefined 6console.log(pathByKey['10210101']); // [ '2', '21', '2101', '10210101' ] 7 8// falseやnullが返ってきて欲しいです。 9console.log(pathByKey['1003'] || false); // false 10console.log(pathByKey['1003'] || null); // null

JavaScriptでは左辺がFalsy(if文に単体で突っ込んでfalseと判定される値、0とかnullとか)の場合、
a || bと記載しておけば右辺を取得出来ます。


【おまけ】

コメント読みました、なるほど…確かに長く業務が動いていたりするとどっかにしわ寄せが行くケースがあります。
わかりました。勉強と実践で役立つ様に最終型も用意しておきました。
最初からこの形式になってくれれば、めっちゃ使いやすいんじゃないですか?

JavaScript

1{ '1': { key: '1', name: '', path: [ '1' ], children: [ '1', '2' ] }, 2 '2': { key: '2', name: '', path: [ '2' ], children: [ '1', '2' ] }, 3 '10': 4 { key: '10', 5 name: '', 6 path: [ '1', '10' ], 7 children: [ '10', '15', '20' ] }, 8 '15': 9 { key: '15', 10 name: '', 11 path: [ '1', '15' ], 12 children: [ '10', '15', '20' ] }, 13 '20': 14 { key: '20', 15 name: '', 16 path: [ '1', '20' ], 17 children: [ '10', '15', '20' ] }, 18 '21': { key: '21', name: '', path: [ '2', '21' ], children: [ '21' ] }, 19 '1001': 20 { key: '1001', 21 name: '', 22 path: [ '1', '10', '1001' ], 23 children: [ '1001', '1002' ] }, 24 '1002': 25 { key: '1002', 26 name: '', 27 path: [ '2', '21', '1002' ], 28 children: [ '1002', '2101' ] }, 29 '2001': 30 { key: '2001', 31 name: '', 32 path: [ '1', '15', '2001' ], 33 children: [ '2001' ] }, 34 '2006': 35 { key: '2006', 36 name: '', 37 path: [ '1', '20', '2006' ], 38 children: [ '2006' ] }, 39 '2101': 40 { key: '2101', 41 name: '', 42 path: [ '2', '21', '2101' ], 43 children: [ '1002', '2101' ] }, 44 '100101': 45 { key: '100101', 46 name: '', 47 path: [ '1', '10', '1001', '100101' ], 48 children: [ '100101', '100106' ] }, 49 '100106': 50 { key: '100106', 51 name: '', 52 path: [ '1', '10', '1001', '100106' ], 53 children: [ '100101', '100106' ] }, 54 '100201': 55 { key: '100201', 56 name: '', 57 path: [ '1', '10', '1002', '100201' ], 58 children: [ '100201' ] }, 59 '10210101': 60 { key: '10210101', 61 name: '', 62 path: [ '2', '21', '2101', '10210101' ], 63 children: [ '10210101' ] } }

Javascript

1> console.log(pathByKey['1001']) 2{ key: '1001', 3 name: '', 4 path: [ '1', '10', '1001' ], 5 children: [ '1001', '1002' ] } 6 7> console.log(pathByKey['100106']) 8{ key: '100106', 9 name: '', 10 path: [ '1', '10', '1001', '100106' ], 11 children: [ '100101', '100106' ] } 12 13> console.log(pathByKey['100201']) 14{ key: '100201', 15 name: '', 16 path: [ '1', '10', '1002', '100201' ], 17 children: [ '100201' ] } 18 19> console.log(pathByKey['1003']) 20undefined 21 22> console.log(pathByKey['10210101']) 23{ key: '10210101', 24 name: '', 25 path: [ '2', '21', '2101', '10210101' ], 26 children: [ '10210101' ] }

対応するコードがこれです。
おまけではこれを基準に解説します。
(多少読みやすくする為にリファクタリングも行っています)

JavaScript

1var obj = { 2 "1": { "name": '', "child": { 3 "10": { "name": '', "child": { 4 "1001": { "name": '', "child": { 5 "100101": { "name": '', "child": {}}, 6 "100106": { "name": '', "child": {}}, 7 }}, 8 "1002": { "name": '', "child": { 9 "100201": { "name": '', "child": {}} 10 }} 11 }}, 12 "15": { "name": '', "child": { 13 "2001": { "name": '', "child": {}} 14 }}, 15 "20": { "name": '', "child": { 16 "2006": { "name": '', "child": {}} 17 }} 18 }}, 19 "2": { "name": '', "child": { 20 "21": { "name": '', "child": { 21 "2101": { "name": '', "child": { 22 "10210101": { "name": '', "child": {}} 23 }}, 24 "1002": { "name": '', "child": {}} 25 }} 26 }} 27}; 28var parse = (obj, path = []) => 29 Object 30 .keys(obj) 31 .map(key => [ 32 { 33 key: key, 34 name: obj[key].name, 35 path: [...path, key], 36 children: Object.keys(obj) 37 }, 38 ...parse(obj[key].child, [...path, key]) 39 ]) 40 .reduce((arr, it) => [...arr, ...it], []); 41var pathByKey = parse(obj).reduce((o, it) => { 42 o[it.key] = it; 43 return o; 44}, {});

今回利用したビルトイン関数は下記です。

a => a + 1

この書き方はES2015で実装されたアロー関数です。
例のa => a + 1function (a) { return a + 1; }です。
(ただし、thisのスコープを新たに作らなかったり、aruguments変数を用意しなかったりと簡易的な関数として振る舞いますので、これが原因でハマる箇所もあります、ちらっと覚えておいてください)

[...path, key]

この書き方もES2015で追加された書き方です。
配列[]やオブジェクト{}の宣言時に頭に...を追加すると、展開してから追加の合図になります。

path.push('123')に似ていますね。
しかし、path.pushは破壊的なメソッドですので、
path.push('123')とするとpathという変数自身に改変が加わってしまいます。
このため、毎回新しくコピーを作り直すという目的でpath.pushの代わりに利用しています。

Object.keys

便利ですね。これはES5の機能なのでガンガン使いましょう。
JSに於けるfor...inはprototypeも取ってくるので、
(アプリを作りたい一般エンジニアにとって)はゴミ以下の価値しかありません。
今回は後述のリスト操作系のメソッドを利用したかったので採用しています。

Array.prototype.map

難解かつ記述量が一気に減るJS使いの必殺技その1です。
これは配列全ての要素に同じ関数を適用し、戻り値を元に配列を再構成するという機能になります。

状態変数を作らずに記述出来るので、上手く使えば行数が減ったり簡素な書き方になることが期待出来ます。

Array.prototype.reduce

難解かつ記述量が一気に減るJS使いの必殺技その2です。
第二引数の値を出発点として、配列の要素数同じ関数を実行して、戻り値を雪だるまのように転がしながら結果を得る手法です。
「畳み込み」と呼ばれる手法ですね。

JavaScript

1var add = (a, b) => a + b; 2var sum = arr => arr.reduce(add, 0); 3console.log(sum([1, 2, 3, 4])); 4// 10
  • 1回目: 0(第二引数の初期値) + 1 -> 1
  • 2回目: 1 + 2 -> 3
  • 3回目: 3 + 3 -> 6
  • 4回目: 6 + 4 -> 10

このような計算を裏で行った結果になります。
add関数を1行にすればsum = arr => arr.reduce((a, b) => a + b, 0)と1行で簡単に宣言出来ますね。
今回は2箇所でこのreduceを使っており、慣れるまでは難解なので解説します。

1個目の使い方はobj.child.child.child...と掘っていくと、
戻り値がどんどんネストしていってしまいます。
イメージとしてはこんな感じ。

JavaScript

1var item = {key: "1010", path ["1", "10", "1010"]}; // objにアクセスするとこんな感じのデータが取得出来る 2var children = [item, [item, [item, [item]]]]; // 再帰関数でどんどん掘っていくとこうなってしまう

これではなんにも使えないですよね。
そこで、毎回第二引数以降のアイテムを一次元配列に引き戻しています。
下記のようなイメージになります。

JavaScript

1var item = {key: "1010", path ["1", "10", "1010"]}; // objにアクセスするとこんな感じのデータが取得出来る 2var children = [[item], [item, item]; // ああ、ネストしてしまったやばい 3var new_children = children.reduce((arr, it) => [...arr, ...it], []); // [item, item, item]

2個目の使い方は空のオブジェクトを宣言して、
1次元配列のアイテムを次々と宣言してくっつけていっています。
こっちは上記の解説の応用ですんなり理解出来そうなので割愛します。

投稿2017/12/09 07:15

編集2017/12/10 07:45
miyabi-sun

総合スコア21158

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

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

kde

2017/12/10 03:50

ご返信遅くなり申し訳ございません。 すごい…!こんなにきれいに書けてしかも結果として扱いやすい形になっていて、想像以上のご回答を頂けて大変ありがたく思います。 > 15キーの配下に2001が入っていたりしますが、これは探索木ですか? これは探索木ではないです。 おっしゃる通り、データ部の設計がおかしくなっているのですが、すでに運用されているデータのためこちらでは手を入れることができず、フロント側でなんとかせざるを得ないという状況です。。 なので、確かに一度すべて開いてみるのが正解だと思いました。 頂いた処理内容のロジックを、今の自分のJS力では理解できていないのですが、じっくりと紐解かせて頂きたいと思います。 このたびは助けて頂き誠にありがとうございます。
miyabi-sun

2017/12/10 07:53

あのコードは流石にデモンストレーション程度のものだったので、 実践用に多少読みやすく、改良加えて、解説もセットで載せました。 ご参考にどうぞ。
kde

2017/12/10 15:59

おおお…まさかここまで丁寧に説明して頂けるなんて…。感激しました…! >最初からこの形式になってくれれば、めっちゃ使いやすいんじゃないですか? 間違いなく使いやすいですね。まさかこんな形式に加工できるなんて思ってもみなかったです。 Array.prototype.mapとArray.prototype.reduceはほぼ使ったことがなく、特にreduceに関してはまだちゃんと理解が追い付いていないのですが、この必殺技を身に付けることで、複雑なデータ操作がスマートに行えそうですね。 教えて頂いたこの内容を反芻して試しながらちゃんと使いこなせるように理解したいと思います。 本当にありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問