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

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

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

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

Q&A

解決済

3回答

9349閲覧

表データをツリー構造のjsonに変換したい

Ecaroh

総合スコア11

JavaScript

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

0グッド

0クリップ

投稿2020/06/19 14:53

編集2020/06/28 09:34
列A列B列C
A-1B-1C-1
A-1B-1C-2
A-1B-2C-3
A-1B-2C-4
A-2B-3C-5
A-2B-3C-6
A-2B-4C-7
A-2B-4C-8
A-1B-5C-9
A-2B-2C-10

このような配列を

javascript

1[ 2 [ '列A', '列B', '列C' ], 3 [ 'A-1', 'B-1', 'C-1' ], 4 [ 'A-1', 'B-1', 'C-2' ], 5 〜〜〜〜 6]

以下のようなツリー構造に変換したい

json

1[ 2 { 3 "列A": "A-1", 4 "list": [ 5 { 6 "列B": "B-1", 7 "list": [ 8 "C-1", 9 "C-2" 10 ] 11 }, 12 { 13 "列B": "B-2", 14 "list": [ 15 "C-3", 16 "C-4" 17 ] 18 }, 19 { 20 "列B": "B-5", 21 "list": [ 22 "C-9" 23 ] 24 } 25 ] 26 }, 27 { 28 "列A": "A-2", 29 "list": [ 30 { 31 "列B": "B-3", 32 "list": [ 33 "C-5", 34 "C-6" 35 ] 36 }, 37 { 38 "列B": "B-4", 39 "list": [ 40 "C-7", 41 "C-8" 42 ] 43 }, 44 { 45 "列B": "B-2", 46 "list": [ 47 "C-10" 48 ] 49 } 50 ] 51 } 52]

mapfilterを多数ネストしできなくもないですが、行列が変更になっても対応できるアルゴリズムが知りたいです。

解決案

javascript

1const tableToTreeObjArr = (tableData, childListKey) => { 2 3 // ヘッダー取得 4 const header = tableData[0] 5 6 // 行単位の処理 7 const addRowToTree = (treeArray, row, header) => { 8 9 let list = treeArray 10 11 for (let i = 0; i < row.length; ++ i) { 12 13 // 最終列以外 子配列を格納する親オブジェクトを生成 14 if (i < row.length - 1) { 15 16 // ヘッダーから列名取得 17 const key = header[i] 18 19 // 親オブジェクトがまだ存在しない場合は生成 20 let obj = list.find(col => { 21 return col[key] === row[i] 22 }) 23 if (!obj) { 24 obj = { [key]: row[i], [childListKey]: [] } 25 list.push(obj) 26 } 27 28 // list変数の参照先を子配列に変更 29 list = obj[childListKey] 30 31 // 最終列 子配列への格納のみ行う 32 } else { 33 list.push(row[i]) 34 } 35 } 36 37 return treeArray 38 } 39 40 // ヘッダー以降の行を配列に畳み込み 41 return tableData.slice(1).reduce((treeArray, row) => { 42 return addRowToTree(treeArray, row, header) 43 }, []) 44}

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

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

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

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

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

think49

2020/06/20 07:11

とりあえず、表の1行目に "list" があると破綻する構造です。
Ecaroh

2020/06/20 07:21

ご確認いただきありがとうございます。 確かにキーが重複してしまいますね。。。 実用的にする為にこの点もハンドリングすることを後ほど検討してみます。
think49

2020/06/20 07:28

Q1. アイデアは有りますが、知りたいのはコードではなく、アルゴリズムという事で良いのですね? Q2. どのあたりまでご自身で考えられましたか。 あなたの考えの軌跡を質問文に追記すると、回答が寄せられやすくなると思います。
Ecaroh

2020/06/20 08:19

>Q1. アイデアは有りますが、知りたいのはコードではなく、アルゴリズムという事で良いのですね? はい。なぜ想定した結果を得ることができるのか、そのアルゴリズムを理解することが一番の目的です。 >Q2. どのあたりまでご自身で考えられましたか。 filterで1列目の重複を取り除き、1列目に属する行をmapで抽出し、また重複を取り除き、、、 とネストを繰り返し記述することで想定通りの結果を得ることはできたものの、行列の変更に耐えられる様に汎用化する上で、 ライブラリを探す?再帰的処理で検討する?関数型をもっと理解するべき?、、、と何から手をつけて良いか分からなくなり投稿させていただいた次第です。
think49

2020/06/20 08:37 編集

うーん…、目的の結果を得られたのであれば、そう難しい問題ではないように思えるのですが…。 配列を前から順番に検索して、予め用意した空のオブジェクトのプロパティに代入するだけですよね? どこが難しく感じるのでしょうか。 (私はライブラリを使用しなくても実装できると思います)
guest

回答3

0

ベストアンサー

こんにちは

いろいろなやり方がありそうですが、一例を回答します。以下の二次元配列 tableData から目的のJSONを得るコードを挙げます。

javascript

1const tableData = [ 2 [ '列A', '列B', '列C' ], 3 [ 'A-1', 'B-1', 'C-1' ], 4 [ 'A-1', 'B-1', 'C-2' ], 5 [ 'A-1', 'B-2', 'C-3' ], 6 [ 'A-1', 'B-2', 'C-4' ], 7 [ 'A-2', 'B-3', 'C-5' ], 8 [ 'A-2', 'B-3', 'C-6' ], 9 [ 'A-2', 'B-4', 'C-7' ], 10 [ 'A-2', 'B-4', 'C-8' ], 11 [ 'A-1', 'B-5', 'C-9' ], 12 [ 'A-2', 'B-2', 'C-10' ] 13];

上記の配列tableDataから、階層構造を復元したオブジェクトを作ります。その際に lodash の _.set を使います。

javascript

1const treeObj = tableData.slice(1).reduce((obj, path) => _(obj).set(path, null).value(), {});

上記によって、treeObj に下記のようなオブジェクトが得られます。

javascript

1{ 2 "A-1": { 3 "B-1": { 4 "C-1": null, 5 "C-2": null 6 }, 7 "B-2": { 8 "C-3": null, 9 "C-4": null 10 }, 11 "B-5": { 12 "C-9": null 13 } 14 }, 15 "A-2": { 16 "B-3": { 17 "C-5": null, 18 "C-6": null 19 }, 20 "B-4": { 21 "C-7": null, 22 "C-8": null 23 }, 24 "B-2": { 25 "C-10": null 26 } 27 } 28}

このtreeObj から、目的の配列を得るための再帰関数 treeObjToArray を作成します。

javascript

1const treeObjToArray = obj => { 2 const entries = Object.entries(obj).sort(([k1], [k2]) => k1.substring(2) - k2.substring(2)); 3 return entries.map(([k, v]) => ({ 4 [`${k[0]}`]: k, 5 list: Object.values(v)[0] ? 6 treeObjToArray(v) : Object.keys(v).sort((k1, k2) => k1.substring(2) - k2.substring(2)) 7 }) 8 ); 9}

上記のtreeObjToArraytreeObj を与えて、目的の配列 treeArray を得ます。

javascript

1const treeArray = treeObjToArray(treeObj); 2 3console.log(JSON.stringify(treeArray, null, 2));

上記によって、以下が出力されます。

json

1[ 2 { 3 "列A": "A-1", 4 "list": [ 5 { 6 "列B": "B-1", 7 "list": [ 8 "C-1", 9 "C-2" 10 ] 11 }, 12 { 13 "列B": "B-2", 14 "list": [ 15 "C-3", 16 "C-4" 17 ] 18 }, 19 { 20 "列B": "B-5", 21 "list": [ 22 "C-9" 23 ] 24 } 25 ] 26 }, 27 { 28 "列A": "A-2", 29 "list": [ 30 { 31 "列B": "B-2", 32 "list": [ 33 "C-10" 34 ] 35 }, 36 { 37 "列B": "B-3", 38 "list": [ 39 "C-5", 40 "C-6" 41 ] 42 }, 43 { 44 "列B": "B-4", 45 "list": [ 46 "C-7", 47 "C-8" 48 ] 49 } 50 ] 51 } 52]

以上参考になれば幸いです。

追記1

別案を検討する際の参考情報です。一次元のオブジェクト配列から、オブジェクト間の階層構造を復元した配列に変換するコードの例は

にいくつか出ています。ご質問にある表データの配列を加工して、上記に投稿されているいずれかの回答コードをベースにした階層化コードを作成する手もあるかもしれません。

追記2

オブジェクトを経由しない別案を作成しました。こちらは、どの階層においても、元のテーブルでの出現順にリストされます。

javascript

1const addRowToTree = (treeArray, row) => { 2 let list = treeArray; 3 for (let i=0; i < row.length; ++ i) { 4 if (i < row.length-1) { 5 const key = `${row[i][0]}`; 6 let obj = list.find(e => e[key] === row[i]); 7 if (!obj) { 8 obj = { [key]: row[i], list: [] }; 9 list.push(obj); 10 } 11 list = obj.list; 12 } else { 13 list.push(row[i]); 14 } 15 } 16 return treeArray; 17}; 18 19const treeArray = tableData.slice(1).reduce((tree, row) => addRowToTree(tree, row), []); 20 21console.log(JSON.stringify(treeArray, null, 2));

追記3

先に書いた、追記1 で、stackoverflow の投稿 Build tree array from flat array in javascript を挙げましたが、これの回答の中のベストアンサーfunction list_to_tree(list) を使えそうか、ちょっとスパイクしてみましたが、問題があり簡単にはできそうもなかったです。その説明を以下、記載します。

元のテーブルのデータ配列は

[ [ '列A', '列B', '列C' ], [ 'A-1', 'B-1', 'C-1' ], [ 'A-1', 'B-1', 'C-2' ], [ 'A-1', 'B-2', 'C-3' ], [ 'A-1', 'B-2', 'C-4' ], [ 'A-2', 'B-3', 'C-5' ], [ 'A-2', 'B-3', 'C-6' ], [ 'A-2', 'B-4', 'C-7' ], [ 'A-2', 'B-4', 'C-8' ], [ 'A-1', 'B-5', 'C-9' ], [ 'A-2', 'B-2', 'C-10' ] ]

というものですが、これのデータ行の3行目 [ 'A-1', 'B-2', 'C-3' ] を、ツリー末端の子(葉)のほうから親のほうへ向かうパスとして読むと

  • C-3 の親は B-2 で、B-2 の親は A-1

と読めるのですが、そうすると、最後の行の [ 'A-2', 'B-2', 'C-10' ]

  • C-10 の親は B-2 で、B-2 の親は A-2

となってしまい、B-2 には、二つの親、A-1 と A-2 があることになってしまいます。ですが、先のstackoverflow の投稿では、各ノードの親は、木構造の定義に沿って、(最大で)一個であるようなノードの一次元配列を木構造化するコードでした。これを踏まえて、それでもベストアンサーfunction list_to_tree(list) を使おうとすると、id として A-1やB-2だったりの文字列はそのまま使えず、「A-1 が親のB-1 と、A-2が親のB-2とは、別物である」という考え方を採り入れて、それらに異なる id を設ける必要があり、これはけっこう煩雑な作業となりそうです。

追記4

上記の追記3で書いた、function list_to_tree(list) を使うための

「A-1 が親のB-1 と、A-2が親のB-2とは、別物である」という考え方を採り入れて、それらに異なる id

を実装したコード例を挙げておきます。以下になります。(※ list_to_tree(list) は、そのままコピペしてきたもので、手を加えていません)

具体的には、たとえば、[ 'A-2', 'B-2', 'C-10' ]C-10 から、以下のようなノードオブジェクトを作成します。

javascript

1{ id: 'A-2/B-2/C-10', parentId: 'A-2/B-2', label: 'C-10' }

また、トップレベルの A-2 からは、 parentId に文字列の "0" を入れた、以下のオブジェクトを作成します。

javascript

1{ id: 'A-2', parentId: '0', label: 'A-2' }

上記のようなオブジェクトを要素とする一次元配列を、元のテーブルデータから作成すると、その配列を function list_to_tree(list) に渡すことができて、木構造化した配列を返してくれますので、それをさらに望ましい形にフォーマットして結果を得ています。

投稿2020/06/19 18:35

編集2020/06/20 10:08
jun68ykt

総合スコア9058

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

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

jun68ykt

2020/06/19 19:56

@Ecarohさん 上記の回答では、目的の配列にする際に、"A-1" などの文字列の末尾にある番号の昇順にソートしているので、元のテーブルのデータ配列とは、一部、順番が変わる可能性があります。たとえば、A-2 のB列の順番は、元のテーブルでの出現順では B-3, B-4, B-2 の順ですが、上記の回答だと、 出力されるJSONでは、B-2, B-3, B-4 と昇順にソートされます。これだとご質問の要件としてよろしくないようでしたら、お知らせください。
jun68ykt

2020/06/19 20:44

上記コメントの問題を解消した別案を、回答のほうに追記2として挙げておきました。
Ecaroh

2020/06/20 07:10

ご丁寧なご回答ありがとうございます。 まずは取り急ぎ御礼申し上げます。 現在、いただいたご回答内容を順に読み解いておりますが、最初の復元したオブジェクト生成する箇所について、差し支えなければ追加で質問させて下さい。 lodashについてはまだまだ勉強不足ですが`_(args)`という書き出しを今回初めてお見受けしました。 `_(args)`を単体で実行してみると`LodashWrapper`というオブジェクトが返ってくることは確認したのですが、 [公式](https://lodash.com/docs/4.17.15)に記述はなく、ググっても読み解ける情報を見つけることはできませんでした。 [setについて公式](https://lodash.com/docs/#set)を確認した上でreduceの戻り値を `_.set(obj, path, null)` に変更したところ同一の値を得ることが確認できたのですが、 `_(obj).set(path, null).value()` とは同義になりますでしょうか。
jun68ykt

2020/06/20 07:41

@Ecarohさん コメントありがとうございます。 > `_(args)`という書き出し は、Lodashのメソッドを、いわゆるメソッドチェーンで書けるようにするための書き方です。以下が参考になります。 Lodashのチェーン記法について (@kurararara さん) https://qiita.com/kurararara/items/fb470ea71e59cd0371d4 上記の投稿の最後にもリンクがありますが、公式ドキュメントでの説明は以下です。 https://lodash.com/docs/4.17.15#prototype-chain > `_(obj).set(path, null).value()` とは同義になりますでしょうか。 はい。同義です。いま見返してみると、_.set の後に何らかlodashのメソッドを続けて書くつもりだったので、_(obj)から始める書き方にしていたようですが、特に他のメソッドは不要でしたので、ご変更頂いた、 > `_.set(obj, path, null)` のほうで問題ありません。引き続きよろしくお願い致します。
Ecaroh

2020/06/20 09:02

>Lodashのチェーン記法について (@kurararara さん) こちらとてもわかりやすい記事でした。ありがとうございます。 引き続きlodash自体の仕様理解に努めたいところです。 >「A-1 が親のB-1 と、A-2が親のB-2とは、別物である」という考え方を採り入れて、それらに異なる id を設ける必要があり、これはけっこう煩雑な作業となりそうです。 こちらは「A-1 が親のB-2 と、A-2が親のB-2とは、別物である」ということですよね。 まさにそのとおりでして、列Cから見て列Bだけでなく列Aと列Bの組み合わせを意識する必要があります。 引き続きいただいた回答内容の確認を進めます。
jun68ykt

2020/06/20 10:11

@Ecarohさん コメントありがとうございます。 > まさにそのとおりでして、列Cから見て列Bだけでなく列Aと列Bの組み合わせを意識する必要があります。 その趣旨で、回答に挙げた、stackoverflow の投稿にあったコードを使った例を、回答のほうに追記4として書きました。各ノードのidを > 列Cから見て列Bだけでなく列Aと列Bの組み合わせを意識した ものに設定して、stackoverflowの回答コードを利用するものになります。参考になれば幸いです。
Ecaroh

2020/06/28 09:35

ご連絡が遅くなりまして申し訳ございません。 ソートの件について返信できておりませんでしたが、今回はテーブル情報を得る時点で想定する並びになっている前提なので考慮していませんでした。 追記の通り、いただいた案を参考に一旦は解決できましたのでベストアンサーとさせていただきました。 ありがとうございました。
jun68ykt

2020/06/28 12:56

どういたしまして???? > いただいた案を参考に一旦は解決できました とのことでよかったです????
guest

0

構造欠陥

オブジェクトの構造に問題があります。

JavaScript

1[ 2 { 3 "列A": "A-1", 4 "list": [ 5 { 6 "列B": "B-1", 7 "list": [ 8 "C-1", 9 "C-2" 10 ] 11 }, 12// 以下略

このオブジェクトを使っている状況で、下記表を取り込んだ場合、

listlist2list3
A1B1C1

"list" のプロパティ名が衝突します。

JavaScript

1[{ 2 "list":"A1", // 列名の "list" 3 "list":[] // 子データ郡の "list" 4}]

要件定義

一つのオブジェクトに格納すべきデータは3つあります。

  • name (列名)
  • value (セル値)
  • children (子のセルデータ集合)

この3つを「どこに保存するか」が鍵となります。

アイデア1: new Map

列名を外に記憶して、データを2つに絞り込めば、new Map が使えます。

JavaScript

1table.header; // ["列A", "列B", "列C"] 2[...table.body.get('A1').get('B1').values()]; // ["C1", "C2"]

アイデア2: DOMノード

DOMがまさにツリー構造のモデルですので、次のDOMノードをDOM APIで作る実装もあります。

HTML

1<div data-key="列A" data-value="A1"><div data-key="列B" data-value="B1"><div data-key="列C" data-value="C1"></div></div></div>

Selectors APIを使えば、目的のノードを見つけるのは容易です。

アイデア3: ユーザ定義class

3種のデータを格納するclassを定義し、new Mao と同じ要領で検索します。

JavaScript

1class CellData { 2 constructor (name, value, children) { 3 this.name = name; // 列名 4 this.value = value; // セル値 5 this.children = children; // 子のセルデータ郡(new Map) 6 } 7 find (value) { 8 return this.children.get(value); 9 } 10} 11 12rootCell.find('A1').name; // "列A" 13rootCell.find('A1').value; // "A1" 14 15rootCell.find('A1').find('B1').find('C1').value; // "C1" 16rootCell.find('A1').children.get('B1').children.get('C1').value; // "C1"

rootCell とは1列目の手前にある、全ての列データ集合を含む「仮想的な列」です。
(全てを含むルートを定義しておくと、データ管理上都合が良い為)

Re: Ecaroh さん

投稿2020/06/20 08:32

think49

総合スコア18166

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

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

Ecaroh

2020/06/28 09:36

ご返答が遅くなりまして申し訳ございません。 追記の通り一旦解決とはしましたが、「そもそもjsonにすることが有用か?」という点も引き続き検討したいと思います。 ご提案いただいた中でも特にclassを定義する方法に興味を持ちました。 classのプロパティを複数持てば1セル内に複数情報を格納するといったより複雑なケースにも対応できるのではないかと考えました。 まずは本件同様に配列から変換するアルゴリズムを考えてみるところから始めてみます。 ご回答ありがとうございました。
guest

0

冗長に

javascript

1var a=[ 2 ['A-1','B-1','C-1'], 3 ['A-1','B-1','C-2'], 4 ['A-1','B-2','C-3'], 5 ['A-1','B-2','C-4'], 6 ['A-2','B-3','C-5'], 7 ['A-2','B-3','C-6'], 8 ['A-2','B-4','C-7'], 9 ['A-2','B-4','C-8'], 10 ['A-1','B-5','C-9'], 11 ['A-2','B-2','C-10'], 12 ]; 13var b={}; 14a.forEach(x=>{ 15 if(typeof b[x[0]]=="undefined"){ 16 b[x[0]]={}; 17 } 18 if(typeof b[x[0]][x[1]]=="undefined"){ 19 b[x[0]][x[1]]=[]; 20 } 21 b[x[0]][x[1]].push(x[2]); 22}); 23console.log(b); 24var c=Object.entries(b).map(x=>{ 25 return {"列A":x[0],list:Object.entries(x[1]).map(y=>{ 26 return {"列B":y[0],list:y[1]}; 27 }) 28 }; 29}); 30console.log(c);

投稿2020/06/20 06:26

yambejp

総合スコア114883

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

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

Ecaroh

2020/06/20 07:11

ご回答ありがとうございます。 現在、いただいたご回答内容を確認中ですが、まずは取り急ぎ御礼申し上げます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問