树的展开(flat)

980 阅读2分钟

介绍

实现数组的flat是一道常见的面试题,那树的flat怎么实现呢? 我也是看了tree-to-list(www.npmjs.com/package/tre…) 的源码,在此总结分享一下。效果就是将Tree上的父子节点都提取出来成一个一级的数组
实现treeToList(arrayTree, 'children'),第一个参数是要处理的树,第二个参数是关键词,根据关键词可以确定父子节点关系

例子

数组树

const arrayTree = [{
    name: 'name1',
    children: [{ 
        name: 'name3',
        children: [{
            name: 'name5'
        }]
    }, {
        name: 'name4'
    }]
}, {
    name: 'name2'
}];

treeToList(arrayTree)

[
        { name: 'name1' },
        { name: 'name3' },
        { name: 'name5' },
        { name: 'name4' },
        { name: 'name2' }
]

对象树

objectTree = {
    node1: {
        name: 'name1',
        tree: {
            node3: {
                name: 'name3',
                tree: {
                    node2: {
                        name: 'name5',
                        key5: 'value5'
                    }
                }
            },
            node4: { name: 'name4' },
        }
    },
    node2: {
        name: 'name2',
        key2: 'value2'
    }
}

treeToList(objectTree, 'tree')

{
    node1: { name: 'name1' },
    node3: { name: 'name3' },
    node4: { name: 'name4' },
    node2: {
        name: 'name2',
        key5: 'value5',
        key2: 'value2'
    }
}

分析

当涉及到层级不定的问题,往往要想到两种方法

  1. 栈的结构
  2. 递归的思想

函数 _transformStack 的作用:取出树上的节点整理成 {value,key}对象放入stack返回

function _transformStack(tree) {
    const stack = [];
    if (Array.isArray(tree)) { // array tree
        for (let index = 0; index < tree.length; index++) {
            const node = tree[index];
            stack.push({
                value: node,
            });
        }
    } else if (Object.prototype.toString.call(tree) === '[object Object]') { // object tree
        for (const key in tree) {
            if (Object.prototype.hasOwnProperty.call(tree, key)) {
                const node = tree[key];
                stack.push({
                    key,
                    value: node,
                });
            }
        }
    }

    return stack;
}

接下来重头戏

function treeToList(tree, key = 'children') {
    let list = [];

    if (Array.isArray(tree)) { // array tree
        list = [];
    } else if (Object.prototype.toString.call(tree) === '[object Object]') { // object tree
        list = {};
    } else { // invalid tree
        return list;
    }

    let stack = _transformStack(tree);

    while (stack.length) {
        const curStack = stack.shift();

        const { key: nodeKey, value: node } = curStack;
        if (!node) continue; // invalid node

        const item = (nodeKey ? list[nodeKey] : {}) || {};
        for (const prop in node) {
            if (Object.prototype.hasOwnProperty.call(node, prop)
                && prop !== key) {
                item[prop] = node[prop];
            }
        }
        if (nodeKey) { // object
            list[nodeKey] = item;
        } else { // array
            list.push(item);
        }

        const subTree = node[key] || [];
        stack = _transformStack(subTree).concat(stack);
    }

    return list;
}

分析下过程

  1. 定义一个list为返回值,如果输入是数组树,list为数组;如果是对象树,list为对象
  2. 首先通过_transformStack获得 {value,key}组成的一个栈stack
  3. 从栈stack中弹出一个对象,取出value。
  4. 遍历value,将key以外的属性直接copy进新对象,新对象放入list。value[key] 通过 _transformStack 处理结果合并入栈,继续遍历stack。
  5. stack为空,处理结束,返回list

总结

整个处理由两个函数完成,主要关注treeToList函数。list储存已展开的节点,stack储存未展开的节点,stack每次弹出一个节点,遇到key属性进入展开后继续放入stack,直到stack为空,树已经展平,返回list。