web-dev-qa-db-ja.com

親フィールドを持つフラットリストから階層ツリーを構築しますか?

parentフィールドを持つ「ページ」オブジェクトのリストがあります。この親フィールドは、リスト内の別のオブジェクトを参照しています。このフィールドに基づいて、このリストからツリー階層を作成したいと思います。

これが私の元のリストのようなものです:

[
  {
    id: 1,
    title: 'home',
    parent: null
  },
  {
    id: 2,
    title: 'about',
    parent: null
  },
  {
    id: 3,
    title: 'team',
    parent: 2
  },
  {
    id: 4,
    title: 'company',
    parent: 2
  }
]

これを次のようなツリー構造に変換したいと思います。

[
  {
    id: 1,
    title: 'home',
    parent: null
  },
  {
    id: 2,
    title: 'about',
    parent: null,
    children:  [
      {
        id: 3,
        title: 'team',
        parent: 2
      },
      {
        id: 4,
        title: 'company',
        parent: 2
      }
    ]
]

私はいつでも任意のリストに対して呼び出すことができる再利用可能な関数を望んでいました。誰かがこれを処理するための良い方法を知っていますか?どんな助けやアドバイスも大歓迎です!

19
TruMan1
function treeify(list, idAttr, parentAttr, childrenAttr) {
    if (!idAttr) idAttr = 'id';
    if (!parentAttr) parentAttr = 'parent';
    if (!childrenAttr) childrenAttr = 'children';

    var treeList = [];
    var lookup = {};
    list.forEach(function(obj) {
        lookup[obj[idAttr]] = obj;
        obj[childrenAttr] = [];
    });
    list.forEach(function(obj) {
        if (obj[parentAttr] != null) {
            lookup[obj[parentAttr]][childrenAttr].Push(obj);
        } else {
            treeList.Push(obj);
        }
    });
    return treeList;
};

フィドル

46
Amadan

受け入れられた答えは私の研究に非常に役立ちましたが、私は理解しているid paramsを精神的に解析し、関数をより柔軟にする必要がありましたが、おそらくアルゴリズムの新しい人を推論するのが少し難しいです。

他の誰かがこの問題を抱えている場合、ここでは基本的に同じコードですが、たぶん簡単に理解できます。

const treeify = (arr, pid) => {
  const tree = [];
  const lookup = {};
  // Initialize lookup table with each array item's id as key and 
  // its children initialized to an empty array 
  arr.forEach((o) => {
    lookup[o.id] = o;
    lookup[o.id].children = [];
  });
  arr.forEach((o) => {
    // If the item has a parent we do following:
    // 1. access it in constant time now that we have a lookup table
    // 2. since children is preconfigured, we simply Push the item
    if (o.parent !== null) {
      lookup[o.parent].children.Push(o);
    } else {
      // no o.parent so this is a "root at the top level of our tree
      tree.Push(o);
    }
  });
  return tree;
};

受け入れられている回答と同じコードで、何が起こっているのかを説明するコメントが付いています。これは、レベルに基づいてインラインmarginLeftインデント付きのページにレンダリングされたdivのリストになる、この使用例です。

const arr = [
  {id: 1, title: 'All', parent: null},
  {id: 2, title: 'Products', parent: 1},
  {id: 3, title: 'Photoshop', parent: 2},
  {id: 4, title: 'Illustrator', parent: 2},
  {id: 4, title: 'Plugins', parent: 3},
  {id: 5, title: 'Services', parent: 1},
  {id: 6, title: 'Branding', parent: 5},
  {id: 7, title: 'Websites', parent: 5},
  {id: 8, title: 'Pen Testing', parent: 7}];
const render = (item, parent, level) => {
  const div = document.createElement('div');
  div.textContent = item.title;
  div.style.marginLeft = level * 8 + 'px';
  parent.appendChild(div);
  if (item.children.length) {
    item.children.forEach(child => render(child, div, ++level));
  }
  return parent;
}
const fragment = document.createDocumentFragment();
treeify(arr)
  .map(item => render(item, fragment, 1))
  .map(frag => document.body.appendChild(frag))

実行したい場合は、Codepen: https://codepen.io/roblevin/pen/gVRowd?editors=001

このソリューションの興味深い部分は、項目のIDをキーとして使用してルックアップテーブルがフラットであり、ルートオブジェクトのみが結果のツリーリストにプッシュされることです。ただし、JavaScriptオブジェクトの参照の性質上、ルートには子があり、子には子などがありますが、基本的にはルートから接続されているため、ツリーのようになっています。

1
Rob