2021-12-28 16:30:26  2080 0

laravel中无限级分类非递归的应用场景

 标签:   

大家好我是猪哥,今天给大家讲解一下无限级分类,在实际工作中的使用,在讲解之前我们先看一段常规的递归是如何使用的我们用一张图来解释下


递归的定义

    递归(http:/en.wikipedia.org/wiki/Recursive)是一种函数调用自身(直接或间接)的一种机制,这种强大的思想可以把某些复杂的概念变得极为简单。在计算机科学之外,尤其是在数学中,递归的概念屡见不鲜。例如:最常用于递归讲解的斐波那契数列便是一个极为典型的例子,而其他的例如阶层(n!)也可以转化为递归的定义(n! = n*(n-1)!).即使是在现实生活中,递归的思想也是随处可见:例如,由于学业问题你需要校长盖章,然而校长却说“只有教导主任盖章了我才会盖章”,当你找到教导主任,教导主任又说:“只有系主任盖章了我才会盖章”...直到你最终找到班主任,在得到班主任豪爽的盖章之后,你要依次返回到系主任、教导主任、最后得到校长的盖章,过程如下:

src=http___img.it610.com_image_info5_212936f611c04cb08818e116ba3daa6d.png&refer=http___img.it610.jpeg

这种方式就如剥洋葱,一层一层的,我们转变成 代码,可以是下面这个样子

/**
 * 格式化栏目列表为树状结构
 * @param $permissions
 * @param int $pid
 * @return array
 */
public function formatNestCategories($categories, $pid = 0)
{
    // 不能用 static
    $result = array();
    foreach ($categories as $v) {
        if ($v['PID'] == $pid) {
            foreach ($categories as $subVal) {
                if ($subVal['PID'] == $v['PID']) {
                    $v['children'] = $this->formatNestCategories($categories, $v['ID']);
                    break;
                }
            }
            $result[] = $v;
        }
    }
    return $result;
}

这里就是不停的找它的子类,直到没有有找到,在返回上一级继续查找,直到循环结束!可见如果数据量很小的情况下,使用效率可以,一旦数据庞大,效率十分底下。如果在浏览器使用会直接崩,因为php 默认超时是30秒,这是传统的递归做法,如果要递归全国省市这种庞大的树结构,使用这种方法就非常不合适!那么有没有高效的做法呢?答案是肯定有

下面我们来看一个 php 的基础知识,php 地址符 & 的应用,这个类似c语言的指针

$a = 1;
$b = $a;
$c = &$a;
$a = 888;
echo $a ."</br>";
echo $c;

image.png

我们可以看到只要我们修改了a的 值 c的值马上就发生了变化,这是因为 c的地址指向了 a 他们使用的是同一个内存地址!由此我们受到启发,我们可不可以使用这个特性来递归全国省市这种数据庞大的树形结构呢?为了方便理解,我们尝试使用一小段省市的数组进行实验

$data = [
  ['id'=>1,'name'=>'淮北市','parent_code'=>3406000,'area_code'=>3406001],
  ['id'=>1,'name'=>'濉溪县','parent_code'=>3406001,'area_code'=>3406002],
  ['id'=>1,'name'=>'南平镇','parent_code'=>3406002,'area_code'=>3406021],
  ['id'=>1,'name'=>'烈山区','parent_code'=>3406001,'area_code'=>3406101],
  ['id'=>1,'name'=>'仁和小区','parent_code'=>3406101,'area_code'=>3406321],
];

我们的思路如下,步骤1: 先将实验数组,重新组合成 area_code 和parent_code 的键值对,area_code 是城市的区域码 是唯一的,parent_code 是它的父级

代码如下:

$items = [];

foreach ($data as  $value) {
    $items[$value[$id]] = $value;
}

我们打印下看看

image.png

可以看到我们已经重新组装成了以 区域码为键 的数组 列表。

步骤2: 我们可以看到上面的数组的键都是以各自的区域码命名的,我们的现在要做的就是在每个区域码代表的键 的数组中拿出它的 父级 ,也就是parent_code .然后去每个数组中找它对应的parent_code

步骤3: 我们回到之前我们讨论的 地址引用的问题,由于 $c 和 $a 他们是引用关系 无论a 怎么变化 c的值始终和 a保持一致 我们利用 内存 指针原理,我们创建一个 空数组

$tree 用来保存 树形结构 代码如下:

foreach ($items  as $k => $v) {

    if (isset($items[$v[$pid]])) {

        $items[$v[$pid]][$son][] = &$items[$v[$id]]; 
    } else {
        $tree[] = &$items[$v[$id]];
    }
}


代码分析 

$items[$v[$pid]][$son][] = &$items[$v[$id]];

这里使用了之前说到的 地址符引用, 我们遍历 步骤1中 重新组装过的 items 数组,我们用新的数组保存了根节点的地址,后面无论根节点怎么变我们的新数组都会和变化过后的数据保持一致;那么不是根节点的数据怎么处理呢,我们这里可以把它的地址放到根的儿子节点当中,这样的话就是一层层引用,最后所有的数据都被新数组引用了。最终我们看到了数据是这样的

image.png

为了方便 我们最终封装成一个通用方法

public function createTree($cate,$id='ID',$pid='PID',$son = 'children')
{
   $items = [];
   $tree = [];
   foreach ($cate as  $value) {
       $items[$value[$id]] = $value;
   }

   foreach ($items  as $k => $v) {

       if (isset($items[$v[$pid]])) {

           $items[$v[$pid]][$son][] = &$items[$v[$id]];
       } else {
           $tree[] = &$items[$v[$id]];
       }
   }
   return $tree;
}

总结:我们可以看到,我们并没有使用常规的递归方法查找这种数据量比较大的树形结构,我们采用了地址引用的方法完成了最终的结果,优点是可见的,虽然我们的测试数据并不多,但是经过测试,70万条数据加载2秒完成,如果使用常规方法将是一场灾难,效率非常底下!由此可见熟练掌握数据结构是多么的重要!感谢能坚持看到最后的同学们。

马上元旦过年了。猪哥在这里祝大家新年快乐,事业有成!