如何在FP JS中从平面列表构建树

时间:2017-02-04 11:25:22

标签: javascript algorithm functional-programming

我正在学习功能性Javascript并遇到问题。 我有这个扁平的物体:

const data = [
                    {id: 1, name: "Folder1", parentId: null},
                    {id: 2, name: "Folder2", parentId: null},
                    {id: 3, name: "Folder3", parentId: 1},
                    {id: 4, name: "Folder4", parentId: 2},
                    {id: 5, name: "Folder5", parentId: 3},
                    {id: 6, name: "Folder6", parentId: 3}
]

我希望将它转换为这个分层对象,只使用纯函数,没有fors,ifs和其他"命令式样式语句"。

结果应该是:

    [{
        id: 1,
        name: "Folder1",
        parentId: null,
        children = [{
            id: 3,
            name: "Folder3",
            parentId: 1,
            children = [{
                    id: 5,
                    name: "Folder5",
                    parentId: 3
                },
                {
                    id: 6,
                    name: "Folder6",
                    parentId: 3
                }
            ]
        }]
    },
    {
        id: 2,
        name: "Folder2",
        parentId: null,
        children = [{
            id: 4,
            name: "Folder4",
            parentId: 2
        }]
    }
]

任何想法?

3 个答案:

答案 0 :(得分:4)

此提案没有if,但有Array#reduceMap。它需要一个排序数组。



var data = [{ id: 1, name: "Folder1", parentId: null }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 6, name: "Folder6", parentId: 3 }],
    tree = data
        .reduce(
            (m, a) => (
                m
                    .get(a.parentId)
                    .push(Object.assign({}, a, { children: m.set(a.id, []).get(a.id) })),
                m
            ),
            new Map([[null, []]])
        )
        .get(null);

console.log(tree);

.as-console-wrapper { max-height: 100% !important; top: 0; }




或者与上面使用ES2015解构分配相同。它需要一个排序数组,取决于只有idnameparentId键的输入数据。



var data = [{ id: 1, name: "Folder1", parentId: null }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 6, name: "Folder6", parentId: 3 }],
    tree = data
        .reduce(
            (m, {id, name, parentId}) => (
                m
                    .get(parentId)
                    .push({id, name, parentId, children: m.set(id, []).get(id) }),
                m
            ),
            new Map([[null, []]])
        )
        .get(null);

console.log(tree);

.as-console-wrapper { max-height: 100% !important; top: 0; }




当然这可能应该写成可重复使用的功能......



var data = [{ id: 1, name: "Folder1", parentId: null }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 6, name: "Folder6", parentId: 3 }];

// pure, reusable function
var buildTree = (data) =>
  data.reduce(
    (m, {id, name, parentId}) => (
      m
        .get(parentId)
        .push({id, name, parentId, children: m.set(id, []).get(id) }),
      m
    ),
    new Map([[null, []]])
  )
  .get(null);

console.log(buildTree(data));

.as-console-wrapper { max-height: 100% !important; top: 0; }




最后,如果数据以未排序的顺序到达,我们可以使用自定义比较器处理排序



// unsorted data example
var data = [{ id: 6, name: "Folder6", parentId: 3 }, { id: 2, name: "Folder2", parentId: null }, { id: 3, name: "Folder3", parentId: 1 }, { id: 4, name: "Folder4", parentId: 2 }, { id: 5, name: "Folder5", parentId: 3 }, { id: 1, name: "Folder1", parentId: null }];

// immutable sort
var sort = (f,xs) => [...xs.sort(f)];

// custom tree comparator
var treeComparator = (x,y) =>
  x.parentId - y.parentId || x.id - y.id; 

// sort data, then reduce
var buildTree = (data) =>
  sort(treeComparator, data).reduce(
    (m, {id, name, parentId}) => (
      m
        .get(parentId)
        .push({id, name, parentId, children: m.set(id, []).get(id) }),
      m
    ),
    new Map([[null, []]])
  )
  .get(null);

console.log(buildTree(data));

.as-console-wrapper { max-height: 100% !important; top: 0; }




答案 1 :(得分:0)

您可以使用递归函数执行此操作,但需要使用reduce循环数组并使用if语句。

const arr = [
  {id: 1, name: "Folder1", parentId: null},
  {id: 2, name: "Folder2", parentId: null},
  {id: 3, name: "Folder3", parentId: 1},
  {id: 4, name: "Folder4", parentId: 2},
  {id: 5, name: "Folder5", parentId: 3},
  {id: 6, name: "Folder6", parentId: 3}
]

function buildTree(data, pId) {
  return data.reduce(function(r, e) {
    var e = Object.assign({}, e);

    if (e.parentId == pId) {
      var children = buildTree(data, e.id)
      if (children.length) e.children = children
      r.push(e)
    }
    return r;
  }, [])
}

console.log(buildTree(arr, null))

答案 2 :(得分:0)

const data = [
   {id: 1, name: "Folder1", parentId: null},
   {id: 2, name: "Folder2", parentId: null},
   {id: 3, name: "Folder3", parentId: 1},
   {id: 4, name: "Folder4", parentId: 2},
   {id: 5, name: "Folder5", parentId: 3},
   {id: 6, name: "Folder6", parentId: 3}
];

function trampoline ( f ) {
    while ( f && f instanceof Function ) { f = f ( ); }
    return f;
}

function buildTree ( data, copy, top = [] ) {

    function recur ( data, copy, top ) {
        copy = copy || data.concat ( [] );
        let current = copy.shift ( );
        current ? doWork ( ) : null; 

        function doWork ( )  {
            top = top.concat ( ( ! current.parentId ? current : [] ) );
            current.children = copy.filter ( x => { return current.id === x.parentId } );
        }

        return ( current ? recur.bind ( null, data, copy, top ) : top );
    }

    return trampoline ( recur.bind ( null, data, copy, top ) );
}

data.map ( x => { x [ 'children' ] = [ ]; return x; } );
console.log ( buildTree ( data ) );