PHP递归函数怎么用于数据筛选 PHP递归函数实现数据过滤的实例分析
在实际的PHP开发场景中,我们经常会遇到层级结构的数据,比如无限级分类、树形菜单、嵌套的评论列表等。这类数据的筛选和过滤如果只用普通的循环很难处理,这时候递归函数就派上了用场。本文将结合实际案例,讲解如何用PHP递归函数实现数据筛选和过滤。
什么是递归函数
递归函数指的是在函数内部调用自身的函数,它适合处理那些可以拆分成相同子问题的任务。使用递归函数处理层级数据时,只需要编写一次处理逻辑,就能自动遍历所有层级的节点,不需要提前知道数据的层级深度。
需要注意的是,递归函数必须设置终止条件,否则会出现无限递归导致程序崩溃。通常终止条件是遍历到没有子节点的层级,或者满足某个筛选条件时停止递归。
递归函数实现数据筛选的核心思路
用递归函数做数据筛选,核心逻辑可以分为三步:
- 遍历当前层级的每一个数据节点
- 判断当前节点是否满足筛选条件,满足则保留,不满足则跳过
- 如果当前节点存在子节点,递归调用函数处理子节点,把子节点的筛选结果合并到当前结果中
实例:筛选无限级分类中状态为启用的数据
假设我们有一个无限级分类的数组,每个分类包含id、name(分类名称)、status(状态,1为启用,0为禁用)、children(子分类数组,没有子分类时为空数组)。现在需要筛选出所有状态为启用的分类,并且保留原有的层级结构。
首先我们看原始的数据结构:
<?php
// 原始无限级分类数据
$categoryList = [
[
'id' => 1,
'name' => '电子产品',
'status' => 1,
'children' => [
[
'id' => 2,
'name' => '手机',
'status' => 1,
'children' => [
[
'id' => 3,
'name' => '智能手机',
'status' => 0,
'children' => []
],
[
'id' => 4,
'name' => '功能手机',
'status' => 1,
'children' => []
]
]
],
[
'id' => 5,
'name' => '电脑',
'status' => 0,
'children' => [
[
'id' => 6,
'name' => '笔记本电脑',
'status' => 1,
'children' => []
]
]
]
]
],
[
'id' => 7,
'name' => '服装',
'status' => 0,
'children' => []
]
];
?>接下来我们编写递归筛选函数,逻辑是:遍历每个分类,先判断当前分类状态是否为1,如果是则保留,再递归处理其子分类,把子分类中符合条件的也合并进来;如果当前分类状态为0,并且没有符合条件的子分类,就不保留该节点。
<?php
/**
* 递归筛选状态为启用的分类
* @param array $list 待筛选的分类数组
* @return array 筛选后的分类数组
*/
function filterEnableCategory(array $list): array
{
$result = [];
foreach ($list as $item) {
// 递归处理子分类,先拿到子分类的筛选结果
$children = [];
if (!empty($item['children'])) {
$children = filterEnableCategory($item['children']);
}
// 判断当前分类是否启用,或者子分类有符合条件的内容
if ($item['status'] == 1 || !empty($children)) {
// 保留当前分类,子分类替换为筛选后的结果
$temp = $item;
$temp['children'] = $children;
$result[] = $temp;
}
}
return $result;
}
// 调用函数筛选数据
$filteredList = filterEnableCategory($categoryList);
// 打印筛选结果
echo '<pre>';
print_r($filteredList);
echo '</pre>';
?>上述代码的执行逻辑是:
- 首先处理id为1的电子产品分类,状态为1,先递归处理其子分类
- 处理子分类中的手机分类(id=2,状态1),再递归处理手机的子分类:智能手机状态0且无子分类,不保留;功能手机状态1,保留。所以手机的children是功能手机
- 处理子分类中的电脑分类(id=5,状态0),递归处理其子分类:笔记本电脑状态1,保留。所以电脑分类虽然自身状态为0,但有符合条件的子分类,因此保留,children是笔记本电脑
- 处理id为7的服装分类,状态0且无子分类,不保留
最终筛选后的结果会保留电子产品、手机(含功能手机)、电脑(含笔记本电脑)这三个层级结构,符合我们的筛选需求。
递归筛选的注意事项
使用递归函数做数据筛选时,有几个点需要特别注意:
- 终止条件要明确:本例中终止条件是遍历完当前层级的children,当children为空时不再递归,避免无限调用
- 避免修改原数据:上面的示例中我们用$temp复制了原节点数据,再修改children属性,不会影响到原始的分类数组,如果需要修改原数据可以调整逻辑
- 性能问题:如果层级非常深或者数据量极大,递归可能会导致栈溢出,这时候可以考虑用迭代加栈的方式实现,不过大部分中小规模的数据用递归完全足够
扩展:带自定义条件的递归筛选
如果筛选条件不是固定的状态启用,而是可以自定义,我们可以给递归函数加一个条件回调参数,让函数更通用。比如下面的示例,支持传入自定义的筛选条件:
<?php
/**
* 通用递归筛选函数
* @param array $list 待筛选的数组
* @param callable $condition 筛选条件回调,接收当前节点,返回bool
* @return array 筛选后的数组
*/
function recursiveFilter(array $list, callable $condition): array
{
$result = [];
foreach ($list as $item) {
$children = [];
if (isset($item['children']) && !empty($item['children'])) {
$children = recursiveFilter($item['children'], $condition);
}
// 当前节点满足条件,或者子节点有符合条件的内容
if ($condition($item) || !empty($children)) {
$temp = $item;
$temp['children'] = $children;
$result[] = $temp;
}
}
return $result;
}
// 自定义筛选条件:筛选id大于2的分类
$filteredById = recursiveFilter($categoryList, function ($item) {
return $item['id'] > 2;
});
echo '<pre>';
print_r($filteredById);
echo '</pre>';
?>这个通用函数可以适配不同的筛选需求,只需要修改传入的回调条件即可,不需要重复编写递归逻辑,代码的可复用性更高。