9行代码太浪费了,5行代码足矣,不用递归实现无限分类数据的树形格式化

2014-04-23 08:18

前言:代码概览

我们知道很多开源软件的无限分类都是采用递归的算法,但是我们知道递归即浪费时间,又浪费空间(内存),实践中,我们一般会在model中查询出格式化成主键值对应数据的形式,因而我们可以直接用这样的数据,就少了一层循环。代码也非常简洁。

先来看看分类方法:arr.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
/**
 * 方法一:
 * 此方法由@Tonton 提供
 * @form http://levi.cg.am
 * @date 2012-12-12
 */
function genTree5(Array $items) {
    foreach ($items as $item)
        $items[$item['pid']]['son'][$item['id']] = &$items[$item['id']];
    return isset($items[0]['son']) ? $items[0]['son'] : array();
}
 
/**
 * 方法二:将数据格式化成树形结构
 * @author Xuefen.Tong
 * @form http://levi.cg.am
 * @param array $items
 * @return array
 */
function genTree9(Array $items) {
    $tree = array();    //格式化好的树
    foreach ($items as $item)
        if (isset($items[$item['pid']]))
            $items[$item['pid']]['son'][] = &$items[$item['id']];
        else
            $tree[] = &$items[$item['id']];
    return $tree;
}

试着输出看看ex1.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
include 'arr.php';
 
$items = array(
    1 => array('id' => 1, 'pid' => 0, 'name' => '江西省'),
    2 => array('id' => 2, 'pid' => 0, 'name' => '黑龙江省'),
    3 => array('id' => 3, 'pid' => 1, 'name' => '南昌市'),
    4 => array('id' => 4, 'pid' => 2, 'name' => '哈尔滨市'),
    5 => array('id' => 5, 'pid' => 2, 'name' => '鸡西市'),
    6 => array('id' => 6, 'pid' => 4, 'name' => '香坊区'),
    7 => array('id' => 7, 'pid' => 4, 'name' => '南岗区'),
    8 => array('id' => 8, 'pid' => 6, 'name' => '和兴路'),
    9 => array('id' => 9, 'pid' => 7, 'name' => '西大直街'),
    10 => array('id' => 10, 'pid' => 8, 'name' => '东北林业大学'),
    11 => array('id' => 11, 'pid' => 9, 'name' => '哈尔滨工业大学'),
    12 => array('id' => 12, 'pid' => 8, 'name' => '哈尔滨师范大学'),
    13 => array('id' => 13, 'pid' => 1, 'name' => '赣州市'),
    14 => array('id' => 14, 'pid' => 13, 'name' => '赣县'),
    15 => array('id' => 15, 'pid' => 13, 'name' => '于都县'),
    16 => array('id' => 16, 'pid' => 14, 'name' => '茅店镇'),
    17 => array('id' => 17, 'pid' => 14, 'name' => '大田乡'),
    18 => array('id' => 18, 'pid' => 16, 'name' => '义源村'),
    19 => array('id' => 19, 'pid' => 16, 'name' => '上坝村'),
);
 
header("Content-Type: text/html; charset=utf-8");
echo "<pre>";
print_r(genTree5($items));
print_r(genTree9($items));
echo "</pre>";

打印结果太长,请自行测试:

原本写到这里就基本差不多了,但是最近有人一直在问这个代码有BUG,于是做几个解答:

问题1、数据规范

从上面代码看出,要分类得数据是要按照严格规范来进行,如下:

  1. 数据类型:排序必须是一个二维数组,不可以是字符串,也不可以大于小于二维数组;
    这段代码的目的就是将平行的数据变成树状结构,如果都已经分类好了,就不要再使用这个方法了,没有意义
  2. 关键ID:分类必须有标准的关键ID;
    比如:一个班级,是拿成绩为标准的关键ID、还是以学号为参照关键ID,如果没有关键ID,我们如何为你分类呢?
  3. 父级ID:默认是0,子分类的父级ID,是所属父级分类的ID;
    问:可以不要父级ID吗?答:可以,那就统一是0咯,那么也就说明都是同级分类,不需要向下分类
  4. 索引规范:索引必须是和对应的关键ID保持一致
    注:数组索引并不强制要求数字索引,也可以是关联索引,这取决于你的关键ID

我之前有发表过一篇文章专门讲解无限分类的原理,大家如果觉得困惑的话,请移步到这里:

PHP无限分类的具体原理分析

http://levi.cg.am/?p=795

问题2、对于索引乱序的数组,这明显就是bug

这应该是质疑声最多的话了,这样的话明显带有几个错误:

  1. 索引并非强制要求数字索引,何来乱序
  2. 索引要求的是和当前关键ID保持一致,而不是说一定要求顺序排列

那么怎样才能使自己的数据按照规范提供:

sql按照关键ID查询,例如:

1
2
3
4
5
6
7
8
9
10
11
12
$data = array();
$result = mysqli::query('SELECT `id`, `pid`, `name` FROM `table`;');
if ($result->num_rows)
{
    while (FALSE != ($row = $this->fetch_assoc()))
    {
        $id = $row['id'];
        $data[$id] = $row;
    }
}
 
$data = genTree5($data);

非sql查询呢,那么你可以自己创建一个索引出来,如下:

1
2
3
4
5
6
7
8
$data = array();
foreach($result as $value)
{
    $id = $value['id'];
    $data[$id] = $value;
}
 
$data = genTree5($data);

问题3、这影响到我对数组排序

比如上面的排序都使按照id来的,这违背了我的排序的意图。看到有朋友这样留言,其实这也是对分类方法进行了误解,我想说明的是:

这个方法只提供了分类,他单纯的为分类而产生,而不是排序,排序是排序,分类是分类,请分开理解。

ok,那如何来排序呢?比如我希望黑龙江在江西上面,赣州又在南昌上面,诸如此类等。刚才也提到了,分类和排序是两个概念,既然这里多了一个分类的“元素”,再来看上面的数据规范,那么应当加上一个新的规范条件:

  • 排序对象:按照特定参数进行排序
    比如:一个年级4个班级,一个班级分8个小组,每个小组按照学号从前到后安排座位,那么这个“学号”,就是排序参照的对象
    注:排序对象可以是关键ID

回到上面城市示例:我希望黑龙江在江西上面,赣州又在南昌上面,怎么做?

增加一个排序对象“num”:

SQL查询版:

1
2
3
4
5
6
7
8
9
10
11
12
$data = array();
$result = mysqli::query('SELECT `id`, `pid`, `name` FROM `table` ORDER BY `num` ASC;');
if ($result->num_rows)
{
    while (FALSE != ($row = $this->fetch_assoc()))
    {
        $id = $row['id'];
        $data[$id] = $row;
    }
}
 
$data = genTree5($data);

非SQL查询,可以通过php二维排序:

array_multisort的方法以前有提过了,这里提供uasort的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ArrSort
{
    private $_name;
    public function __construct($name)
    {
        $this->_name = $name;
    }
 
    public function num($x, $y)
    {
        return (Int)$x[$this->_name] > (Int)$y[$this->_name];
    }
 
    private function str($x, $y)
    {
        return strcasecmp($x[$this->_name], $y[$this->_name]);
    }
};
 
// 根据数字进行排序
uasort($data, array(new ArrSort('num'), 'num'));
 
// 根据字符进行排序
uasort($data, array(new ArrSort('char'), 'str'));

临时排序,不增加关键字进行排序

我们增加点难度,比如上海,我们想要搞特殊,把他单独提升到第一位,怎么做呢?

代码如下:

1
2
3
4
5
6
7
8
9
10
11
$data = array();
foreach($result as $key => $val)
{
    if ($val['name'] == '上海')
    {
        $data[$key] = $val;
        unset($result[$key]);
    }
}
 
$data = array_merge($data, $result);

如果大家有兴趣,自己可以试着想想把上海放在中间特定某一个位置怎么做,把上海放在末位又该怎么做。

总结

以上只是列举了一小部分,方法有很多,大家自己去发掘。上面这个分类的方法还是非常好用的,他让我们告别了繁琐的递归,提高了我们代码执行效率,方法已提供了,不能因为不会用就说这个方法是有bug。这就好比提供给你碗和筷子了,但是饭还是要自己盛来吃的嘛。

相关资料:

^