算法之生成评论树
本节内容
- 问题由来
- 递归实现
- 高效实现
- 总结
1. 问题由来
项目中用到了展现用户多级评论的功能,可是在数据库中存储的每行数据以前是经过parent_id来标示他们之间的关系。
从数据库中取出这一行行的数据,须要转换成相似于json数据格式的类型(其实就是经过parent_id将评论关联起来生成一颗一颗的评论树),再将数据传递给前端进行渲染。前端
[
{"id":"4","pid":"1","name":"你们电"},
{"id":"5","pid":"1","name":"生活电器"},
{"id":"1","pid":"0","name":"家用电器"},
{"id":"2","pid":"0","name":"服饰"},
{"id":"3","pid":"0","name":"化妆"},
{"id":"7","pid":"4","name":"空调"},
{"id":"8","pid":"4","name":"冰箱"},
{"id":"9","pid":"4","name":"洗衣机"},
{"id":"10","pid":"4","name":"热水器"},
{"id":"11","pid":"3","name":"面部护理"},
{"id":"12","pid":"3","name":"口腔护理"},
{"id":"13","pid":"2","name":"男装"},
{"id":"14","pid":"2","name":"女装"},
{"id":"15","pid":"7","name":"海尔空调"},
{"id":"16","pid":"7","name":"美的空调"},
{"id":"19","pid":"5","name":"加湿器"},
{"id":"20","pid":"5","name":"电熨斗"}
]
相似这样的数据要转换成下面这种的数据:python
[
{"id":"1","pid":"0","name":"家用电器", "chindren":[
{"id":"4","pid":"1","name":"你们电", "chindren":[
{"id":"7","pid":"4","name":"空调", "chindren":[
{"id":"15","pid":"7","name":"海尔空调"},
{"id":"16","pid":"7","name":"美的空调"}
]},
{"id":"8","pid":"4","name":"冰箱"},
{"id":"9","pid":"4","name":"洗衣机"},
{"id":"10","pid":"4","name":"热水器"}
]},
{"id":"5","pid":"1","name":"生活电器","chindren":[
{"id":"19","pid":"5","name":"加湿器"},
{"id":"20","pid":"5","name":"电熨斗"}
]}
]},
{"id":"2","pid":"0","name":"服饰","chindren":[
{"id":"13","pid":"2","name":"男装"},
{"id":"14","pid":"2","name":"女装"}
]},
{"id":"3","pid":"0","name":"化妆","chindren":[
{"id":"11","pid":"3","name":"面部护理"},
{"id":"12","pid":"3","name":"口腔护理"}
]}
]
实现这种需求有两种方式,下面就这两种方式进行一下分析。web
2. 递归实现
class Node:
@staticmethod
def digui(rst,row):
for i in rst:
if row["parent_id"]==i["id"]:
row["children"]=[]
i["children"].append(row)
else:
Node.digui(i["children"],row)
@staticmethod
def build_tree(comment_list):
rst=[]
for row in comment_list:
if row["parent_id"]==None:
row["children"]=[]
rst.append(row)
else:
Node.digui(rst,row)
return rst
comment_list=models.Comment.objects.filter(news_id=new_id)
print(Node.build_tree(comment_list))
这样虽然能实现效果,可是存在了一些问题:算法
- 使用了递归,使得程序的执行效率大大下降。
- 而且这还存在一个缺陷,那就是父亲行必须在儿子行前面,不然儿子数据将会丢失。(固然,能够经过数据库查询出评论数据来以后对评论时间进行排序,可是这样作又会消耗时间在对数据进行排序上,对数据库也是一个压力)
那么有没有其余更高效的解决这个问题的办法呢?
答案是有的,就在下一节。数据库
3. 高效实现
在说明高效实现以前,先补充两个知识点:json
- python中的字典和列表都是引用数据类型,里面就是指针指向的内存地址,当修改实际的内存中的值时,全部指向该地址的指针对应的值都被改变。
- python中的dict数据类型是键值对的形式,内部实现是经过hash实现的,因此经过key获取值的速度比列表获取值的速度快不少。
接下来就是代码实现了:数组
comment_list=models.Comment.objects.filter(news_id=new_id)
ret=[]
comment_list_dict={}
for row in comment_list:
row.update({"children":[]})
comment_list_dict[row["id"]]=row
for item in comment_list:
parrent_row=comment_list_dict.get(item["parent_id"])
if not parrent_row:
ret.append(item)
else:
parrent_row["children"].append(item)
print(ret)
上面的重点那行没法理解能够先看下面的解释的小例子:app
In [1]: a=[1,2,3]
In [2]: b=[4,5,6]
In [3]: a.append(b)
In [4]: a
Out[4]: [1, 2, 3, [4, 5, 6]]
In [5]: b.append(7)
In [6]: b
Out[6]: [4, 5, 6, 7]
In [7]: a
Out[7]: [1, 2, 3, [4, 5, 6, 7]]
数组a中引用了数组b,当数组b的值发生改变的时候,数组a中的对应的数组b中的值也相应的发生了改变,由于a中的引用和b指向了同一块内存地址。函数
使用这种方式只须要对取到的数据列表进行两次遍历就能实现,第一次遍历是为了建立dict,经过dict的key获取值的时间复杂度基本是个常量,不会随着数据量的增加而增加O(1)。第二次遍历数据列表用到了列表和字典的引用知识。性能
这种方式解决了上面递归实现中的两个问题:
- 没有使用递归实现,效率将会大大提高。
- 父亲评论和子评论的顺序之间没有先后关系的约束。
4. 总结
平时在写代码过程当中,须要思考性能带来的问题,当涉及到频繁调用以及大量数据处理时,更是要考虑性能形成的瓶颈。善于思考使用更少的资源实现相同效果将能大大提高程序的执行效率。