TOC
对某个树形结构进行过滤是一个比较常见的场景,但同时想要写出没有问题,并且简洁的代码也并非易事,本文将对这种算法进行简单的分析,并在最后给出代码实例和使用实例。
这是一个树形结构,接下来我会基于这个树进行讲解
A
/ \
B C
/ \
E D
假设我们拥有一个树形递归结构,其中节点内部存放的是某些数据,每个节点会拥有任意数量的子节点,接下来我们需要对整个树进行裁剪。
我们可以发现有两种基本的裁剪模式
1. 父元素优先,从上到下
这种裁剪的特点是,当一个父节点经过判断之后,发现是一个可以被保留的节点的时候,会直接保留整个子树,如果这个父节点自身并不满足保留条件,那么会继续向下递归检查,如果有一个子节点是满足保留条件的,这个父节点也会保留。
假设我们进行对上面的树进行筛选条件为,名字里面包含字母B,那么结果为
A
/
B
/ \
E D
这种情况比较适用于对于关键字进行筛选的功能,比如搜索某个部门的名称,同时也要保留部门下面的所有人员
1. 子元素优先,从下到上
这种裁剪的特点,其实就是上方的模式,但是取消父节点符合保留条件下,对于子树的直接保留,必须进一步对子树进行筛选
假设我们进行对上面的树进行筛选条件为,名字里面包含字母B,那么结果为
A
/
B
假设我们进行对上面的树进行筛选条件为,名字里面包含字母E,那么结果为
A
/
B
/
E
这种筛选比较适用于对于叶子节点进行类型筛选,比如想要筛选类型为X的所有人员,这种情况下是不允许整个树的叶子节点包含任何非该类型的节点的。
代码
有了基本顺序的分析,我们就能比较好的写出一个比较通用的过滤算法
const filterTreeData = (data, isFilterIn, childKey, isParentSave) => {
if (!data) return null
// 由于启动父优先级裁剪,如果匹配成功,保留所有子节点
if (isParentSave && isFilterIn(data)) {
return data
}
const children = (data && data[childKey]) ? data[childKey].map((child) => filterTreeData(child, isFilterIn, childKey, isParentSave)).filter(v => !!v) : []
// 该节点本身匹配失败,并且也没有子节点能够匹配
if (children.length === 0 && !isFilterIn(data)) return null
return {
...data,
children
}
}
使用实例
实际使用的时候,我们可以进行多次过滤,来组合使用,比如先进行关键字筛选,再进行类型筛选,想要在一次递归之中完成两种不同的裁剪方式(上面的两种过滤裁剪模式)是不可能的。
// 先进行关键字筛选
let res = filterTreeData(treeData[0], (data) => {
if (!keyword) return true
return data[labelKey].includes(keyword)
}, childKey, true)
// 再进行类型筛选
res = filterTreeData(res, data => {
if (activeTabName === 'doctor') return data[titleKey] === '医生'
if (activeTabName === 'nurse') return data[titleKey] === '护士'
return true
}, childKey, false)