算法之生成評論樹
本節內容
- 問題由來
- 遞歸實現
- 高效實現
- 總結
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. 總結
平時在寫代碼過程當中,須要思考性能帶來的問題,當涉及到頻繁調用以及大量數據處理時,更是要考慮性能形成的瓶頸。善於思考使用更少的資源實現相同效果將能大大提高程序的執行效率。