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 + 1
はfunction (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次元配列のアイテムを次々と宣言してくっつけていっています。
こっちは上記の解説の応用ですんなり理解出来そうなので割愛します。