让你像数组一样操作Tree树结构

可以这么说,小到一个生命体,大到整个宇宙,世间万物皆是树。

现实生活中最常见的树的例子是家谱,或是公司的组织架构图。

让你像数组一样操作Tree树结构_第1张图片

树是一种非顺序数据结构,一种分层数据的抽象模型,它对于存储需要快速查找的数据非常有用。

一个树结构包含一系列存在父子关系的节点。每个节点都有一个父节点(除了顶部的第一个 节点)以及零个或多个子节点。

JS里面没有像Array一样直接的一个Tree对象,也没有处理Tree的函数,比如:遍历、查找、插入、删除等等,因此我们得自己造轮子

 

1、each

就像Array数组一样,遍历是其他很多的操作的前提,Tree也是一样

while循环栈方式遍历,类似Array.forEach:

/**
 * 遍历树结构方法(while循环栈)
 * @param {Object|Array}  data  传入的数据
 * @param {function} handler 回调函数,处理每个节点,参数为当前节点对象,返回false会终止遍历
 * @param {String} mode 广度优先搜索/深度优先搜索(wide,deep),默认:广度优先
 */
each: function (data, handler, mode) {
    var stack = [].concat(data);
    var node, children, handleRes;

    while (stack.length) {
        node = stack.shift();
        children = node.children
        handleRes = handler && handler(node);

        if (handleRes === false) break;
        if (Array.isArray(children)) {
            mode == 'deep' ?
                [].unshift.apply(stack, children) :
                [].push.apply(stack, children);
        }
    }
}

递归+栈配合方式遍历:

/**
 * 遍历树结构方法(递归+栈配合)
 * @param {Object|Array}  data  传入的数据
 * @param {function} handler 回调函数,处理每个节点,参数为当前节点对象,返回false会终止遍历
 * @param {String} mode 广度优先/深度优先(wide,deep),默认:广度优先
 */
eachRecursive: function (data, handler, mode) {
    var stack = [], index = -1, next, isBreak;
    var isDeepMode = mode === 'deep';
    var recursive = function (data) {
        var node, nodes = [].concat(data), children;

        for (var i = 0; i < nodes.length; i++) {
            node = nodes[i]
            children = node.children || []
            isBreak = handler.call(node, node)

            if (isBreak === false) break
            if (children.length) {
                [].push.apply(stack, children)
                isDeepMode && recursive(children)
            }

            index++
        }

        next = stack[index]
        if (isBreak !== false && !isDeepMode && next) recursive(next)
    }

    recursive(data)
}

特点:

  • 支持一个或多个顶点
  • 支持中断
  • 支持广度/深度优先

2、map

 遍历树并返回新的tree方法,利用递归进行遍历,类似Array.map

/**
 * 遍历树并返回新的tree方法,利用递归进行遍历
 * @param {Object|Array}  data  传入的数据
 * @param {function} handler 处理函数,入参为当前节点
 * @return {Object|Array} 新的tree数据
 */
map: function (data, handler) {
    var stack = [].concat(data);
    var getNodes = (data, handler) => {
        return data.map(v => {
            let node = handler.call(data, v)

            if (Array.isArray(v.children) && v.children.length) {
                node.children = getNodes(v.children, handler)
            }

            return node
        })
    }

    return getNodes(stack, handler)
}

3、find

查找树并返回结果方法,类似Array.find

/**
 * 查找树并返回结果方法,类似Array.find
 * @param {Object|Array}  data  传入的数据
 * @param {function} handler 处理函数,入参为当前节点
 * @return {Object} node节点或null
 */
find: function (data, handler) {
    var result = null

    this.each(data, node => {
        if (handler.call(data, node)) {
            result = node
            return false
        }
    })

    return result
}

4、filter

过滤树节点方法,类似Array.filter

/**
 * 过滤树节点方法,类似Array.filter
 * @param {Object|Array}  data  传入的数据
 * @param {function} handler 处理函数,入参为当前节点
 * @return {Object} node节点
 */
filter: function (data, handler) {
    var result = [], handleRes

    this.each(data, node => {
        handleRes = handler.call(data, node)
        if (handleRes === false) return false
        if (handleRes) result.push(node)
    })

    return result
}

5、some

匹配某一个节点并返回匹配结果的方法,类似Array.some

/**
 * 匹配某一个节点并返回匹配结果的方法,类似Array.some
 * @param {Object|Array}  data  传入的数据
 * @param {function} handler 处理函数,入参为当前节点
 * @return {Object} Boolean
 */
some: function (data, handler) {
    var result = false

    this.each(data, node => {
        if (handler.call(data, node)) {
            result = true
            return false
        }
    })

    return result
}

6、every

匹配每一个节点并返回匹配结果的方法,类似Array.every

/**
 * 匹配每一个节点并返回匹配结果的方法,类似Array.every
 * @param {Object|Array}  data  传入的数据
 * @param {function} handler 处理函数,入参为当前节点
 * @return {Object} Boolean
 */
every: function (data, handler) {
    var result = true

    this.each(data, node => {
        if (!handler.call(data, node)) {
            result = false
            return false
        }
    })

    return result
}

7、flat

树的扁平化方法(树 --> 数组),不改变原数据,返回新的数组

/**
 * 树的扁平化方法(树 --> 数组),不改变原数据,返回新的数组
 * @param {Object} data 传入的数据
 * @param {String} mode 广度优先搜索/深度优先搜索,默认:广度优先(wide,deep)
 * @param {function} callback 回调函数,处理每个节点,参数为当前节点对象
 * @return {Array} result 新的数组
 */
flat: function (data, callback, mode) {
    var result = [];
    var handleNode = function (node, children) {
        callback && callback.apply(node, [node, children]);
        result.push(node);
    }

    this.each(data, handleNode, mode);
    return result;
}

8、reduce

树累加器,改变原数据

/**
 * 树累加器,改变原数据
 * @param {Array|Object} data 数组或对象
 * @param {Object} fields 键值对的对象,键:在data新建的键名,值:data里的字段值
 */
reduce: function (data, fieldsObj, callback) {
    if (!fieldsObj) return data;

    var handleNode = function (node, children) {
        // 累加当前节点
        for (var key in fieldsObj) {
            node[key] = node[key] || [];
            node[key].push(node[fieldsObj[key]]);
        }

        // 回调处理
        // callback && callback(node, children
        callback && callback.apply(this, arguments);

        // 累加children
        children && children.length && children.forEach(function (item) {
            for (var key in fieldsObj) {
                item[key] = [].concat(node[key]);
            }
        });
    };

    this.each(data, handleNode);
}

9、completer

补全父级链方法,传入任意节点,通过已有的数据查找并发回包括所有父级的集合

/**
 * 补全父级链方法,传入任意节点,通过已有的数据查找并发回包括所有父级的集合
 * @param {Array} nodes 节点id数组
 * @param {Array} data 原始数据(未转换tree)
 * @return {Array} result 包括所有父级的集合
 */
completer: function (nodeIds, data) {
    if (!data) return [];
    if (!Array.isArray(nodeIds) || !nodeIds.length) return [];

    var result = [];
    var resultMap = {};
    var dataMap = data.reduce((total, curr) => {
        total[curr.id] = curr
        return total
    }, {})
    var getNode = function (id) {
        var node = dataMap[id];

        if (!node || resultMap[id]) return

        resultMap[id] = 1;
        result.push(node);
        getNode(node.parentId);
    };

    // 生成结果
    nodeIds.forEach(function (id) {
        getNode(id);
    });

    return result;
}

10、append

插入节点方法,传入任意节点ID,在其下插入子节点

/**
 * 插入节点方法,传入任意节点ID,在其下插入子节点
 * @param {String} nodeId 节点id
 * @param {Array} children 插入的字节点
 */
append: function (nodeId, children) {
    if (nodeId == undefined) return;
    if (!Array.isArray(children) || !children.length) return;

    var node = this.find(n => n.id === nodeId)

    if (!node) return;
    if (!node.children) node.children = [];

    [].push.apply(node.children, children)
}

11、TreeFrom

数组 --> 树,利用堆栈转换,不改变数组本身,返回新的对象,支持乱序,类似Array.from

一次遍历实现:

/**
 * 数组 --> 树,利用堆栈转换,不改变数组本身,返回新的数组或对象,支持乱序
 * @param  {Array}  data  数组
 * @return {Array|Object} 数组或对象
 * 原数据要求:top顶点的parentId必须为0或者null/undefined
 */
TreeFrom: function (data) {
    if (!data || !data.length) return {};

    var copy = data.map(function (v) {
        return Object.assign({}, v)
    });
    var id, pid, node, parent;
    var hash = {};
    var tops = [];

    copy.forEach(function (item) {
        id = item.id;
        pid = item.parentId;
        node = hash[id]

        // 如果已存提前存在,证明是父亲节点,先连接他的孩子,支持乱序的重点
        if (node) item.children = node.children;

        hash[id] = item;

        if (pid) {
            parent = hash[pid] = hash[pid] || {};
            parent.children = (parent.children || []).concat(item);
        } else {
            tops.push(item);
        }
    });

    return tops;
}

二次遍历实现:

/**
 * 数组 --> 树,利用堆栈转换,不改变数组本身,返回新的对象,支持乱序
 * @param  {Array}  data  数组
 * @return {Object} 数组或对象
 * 原数据要求:top顶点的parentId必须为0或者null/undefined
 */
TreeFrom2: function (data) {
    if (!data || !data.length) return {};

    var hash = {};
    var tops = [];
    var node, parent
    var copy = data.map(function (v) {
        return Object.assign({}, v)
    });

    // 罗列所有节点
    copy.forEach(function (item) {
        hash[item.id] = item;
    });
    // console.log('hash --> ', hash)

    // 连接所有节点
    for (var item in hash) {
        node = hash[item];
        parent = hash[node.parentId];

        if (!parent) tops.push(node);
        else parent.children = (parent.children || []).concat(node);
    };

    return tops;
}

你可能感兴趣的:(数据/算法,JS)