JS笔试题:扁平数组转树状结构

344 阅读2分钟

题目:

const flatArr = [
    { id: 1, name: '爷爷1', pid: 0 },
    { id: 2, name: '爷爷2', pid: 0 },
    { id: 123, name: '儿子1', pid: 12 },
    { id: 211, name: '儿子2', pid: 21 },
    { id: 11, name: '爸爸1', pid: 1 },
    { id: 12, name: '爸爸2', pid: 1 },
    { id: 21, name: '爸爸3', pid: 2 },
]

function transform (arr) {
    // ...
}

const resultArr = transform (flatArr)
console.log(resultArr)
[
    { 
        id: 1,
        name: '爷爷1',
        pid: 0,
        children: [
            {
                id: 11,
                name: '爸爸1',
                pid: 1
            },
            {
                id: 12,
                name: '爸爸2',
                pid: 1,
                chilren: [
                    { id: 123, name: '儿子1', pid: 12 },
                ]
            },
        ]
    },
    { 
        id: 2,
        name: '爷爷2',
        pid: 0,
        children: [
            {
                id: 21,
                name: '爸爸3',
                pid: 2,
                chilren: [
                    { id: 211, name: '儿子2', pid: 21 }
                ]
            },
        ]
    },
]

思路:涉及树状结构先想递归,拆解步骤,按题目给出的数组来看,循环先将pid为0的元素放入结果数组中,放入的这一刻查询该元素的id是否为数组其他元素的pid,如果是,循环找出这些元素将其放入该元素的children中,以此做深度遍历。

递归解答:

function transform (arr) {
    function func (arr, pid) {
        const res = []
        for (const elem of arr) {
            if (elem.pid === pid) {
                res.push(elem)
                if (arr.find(item => item.pid === elem.id)) {
                    res[res.length - 1].children = func(arr, elem.id)
                }
            }
        }
        return res
    }
    
    return func(arr, 0)
}

结果打印出来确实是能实现题目要求的结果,但是在实际的开发中,我们的树形结构往往属性众多且复杂,递归会占用很大的内存,也可能会造成栈溢出,所以我又想了其他的一些解法

利用“引用类型浅拷贝”解答:

function transform (arr) {
    arr.forEach(elem => {
        if (elem.pid) {
            const pIndex = arr.findIndex(item => item.id === elem.pid)
            if (pIndex > -1 && !arr[pIndex].children) {
                arr[pIndex].children = []
            }
            arr[pIndex].children.push(elem)
        }
    })
    return arr.filter(item => !item.pid)
}

根据上面的解答,我们再进行进一步的优化,通过提前声明一个结果数组来代替最后的filter循环,通过提前将id与索引值建立map联系来减少原本forEach里面还要每次再通过findIndex来找父元素的循环,优化如下:

function transform (arr) {
    const results = []
    const map = new Map(arr.map((item, index) => [item.id, index]))
    
    for (let i = 0; i < arr.length; i++) {
        if (arr[i].pid) {
            const index = map.get(arr[i].pid)
            arr[index].children = Array.isArray(arr[index].children)
                ? [...arr[index].children, arr[i]]
                : [arr[i]]
            continue
        }
        results.push(arr[i])
    }
    return results
}