監控一個地區網絡設備的性能指標,會通過報表或告警展現,報表或告警往往只關心部分設備,此時在數據查詢中我們就會進行設備實體過濾。實體樹過濾是一種常見的過濾方式,但是網絡設備數量巨大,我們不可能在頁面上加載所有實體,前台也就無法把用戶選擇的所有實體(葉子節點)傳遞到后台,這時候就不能簡單的采用in條件來過濾選擇實體,我們必須綜合使用in,not in,=,!=來過濾實體。
樹顯然是一種遞歸的數據結構,那么解析它必然就要使用遞歸算法。
一、從例子開始
下圖是一棵勾選了的網元實體樹,從圖上我們可以看出以下幾點
1、 網元層級關系為 Prov <- City <- BSC <- BTS <- Cell;
2、 實心方框為勾選節點,空心方框為去勾選節點;
3、 紅色加粗節點為前台傳遞到后台的節點信息(json格式);
4、 右側sql是根據前台傳遞的節點信息解析出來的實體過濾條件。
二、算法歸納
1、 同層節點條件之間的關系為or;
2、 被勾選的節點與子節點條件之間的關系是and;
3、 去勾選的節點與子節點條件之間的關系是or;
4、 節點與父節點勾選狀態相同,則不必解析該節點條件,直接解析子節點條件;
5、 節點與父節點勾選狀態不同,先解析該節點條件,再解析子節點條件;
6、 節點沒有子節點,則直接返回該節點條件。
三、遞歸算法原理
1、 算法重復被自身調用;
2、 存在出口條件。
顯然,例子中每個節點的解析算法一致,並且節點遞歸嵌套,滿足條件1;
出口條件為節點不存在葉子節點。
四、代碼實現
1、 節點代碼TreeNode
package com.coshaho.learn.recursion; import java.util.List; /** * * Node.java Create on 2017年5月19日 下午10:34:13 * * 類功能說明: 樹節點定義 * * Copyright: Copyright(c) 2013 * Company: COSHAHO * @Version 1.0 * @Author coshaho */ public class TreeNode { private String name; private String type; private boolean isCheck; private List<TreeNode> children; public TreeNode(String name, String type, boolean isCheck) { this.name = name; this.type = type; this.isCheck = isCheck; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public List<TreeNode> getChildren() { return children; } public void setChildren(List<TreeNode> children) { this.children = children; } public boolean isCheck() { return isCheck; } public void setCheck(boolean isCheck) { this.isCheck = isCheck; } }
2、 遞歸解析算法TreeSqlTranslator
package com.coshaho.learn.recursion; import org.springframework.util.CollectionUtils; /** * * TreeSqlTranslator.java Create on 2017年5月19日 下午11:38:07 * * 類功能說明: 實體書過濾條件翻譯 * * Copyright: Copyright(c) 2013 * Company: COSHAHO * @Version 1.0 * @Author coshaho */ public class TreeSqlTranslator { public String parseTree2Sql(TreeNode root) { if(root.isCheck()) { return "1=1 and (" + parseChildrenNode2Sql(root) + ')'; } return parseChildrenNode2Sql(root); } /** * 解析子節點為sql * * @author coshaho * @param node * @return */ private String parseChildrenNode2Sql(TreeNode node) { StringBuffer childCondition = new StringBuffer(); for(TreeNode child : node.getChildren()) { // 1、同層節點之間采用or拼接條件 childCondition.append('(').append(parseNode2Sql(child, node.isCheck())).append(") or "); } // 此處代碼可以減少非必須的括號 if(1 == node.getChildren().size()) { return childCondition.substring(1, childCondition.length() - 5); } else { return childCondition.substring(0, childCondition.length() - 4); } } /** * 解析單個節點為sql * * @author coshaho * @param node * @param isCheck * @return */ private String parseNode2Sql(TreeNode node, boolean isCheck) { boolean nodeCheck = node.isCheck(); // 2、沒有子節點,則直接返回該節點條件 if(CollectionUtils.isEmpty(node.getChildren())) { return generateNodeSql(node); } StringBuffer condition = new StringBuffer(); // 3、父節點與當前節點勾選狀態不一致,拼接當前節點條件 if(isCheck ^ nodeCheck) { condition.append(generateNodeSql(node)); // 4、當前節點被勾選,則與子節點關系為and if(node.isCheck()) { // 拼接子節點條件 return condition.append(" and (") .append(parseChildrenNode2Sql(node)).append(')').toString(); } // 5、當前節點去勾選,則與子節點關系為or else { // 拼接子節點條件 return condition.append(" or (") .append(parseChildrenNode2Sql(node)).append(')').toString(); } } // 6、父節點與當前節點勾選狀態一致,直接處理子節點 else { return parseChildrenNode2Sql(node); } } private String generateNodeSql(TreeNode node) { if(node.isCheck()) { return node.getType() + " = '" + node.getName() + '\''; } else { return node.getType() + " != '" + node.getName() + '\''; } } }
3、 測試代碼TreeSqlTranslatorTest
package com.coshaho.learn.recursion; import java.util.ArrayList; import java.util.List; /** * * TreeSqlTranslatorTest.java Create on 2017年5月19日 下午11:39:25 * * 類功能說明: 遞歸算法測試 * * Copyright: Copyright(c) 2013 * Company: COSHAHO * @Version 1.0 * @Author coshaho */ public class TreeSqlTranslatorTest { public static void main(String[] args) { TreeNode All = new TreeNode("All", "All", false); TreeNode GD = new TreeNode("GD", "Prov", true); TreeNode JS = new TreeNode("JS", "Prov", true); TreeNode SZ = new TreeNode("SZ", "City", false); TreeNode NJ = new TreeNode("NJ", "City", true); TreeNode SZBSC3 = new TreeNode("SZBSC3", "BSC", false); TreeNode NJBSC1 = new TreeNode("NJBSC1", "BSC", false); TreeNode NJBSC3 = new TreeNode("NJBSC3", "BSC", true); TreeNode SZBTS1 = new TreeNode("SZBTS1", "BTS", true); TreeNode SZBTS3 = new TreeNode("SZBTS3", "BTS", true); TreeNode NJBTS3 = new TreeNode("NJBTS3", "BTS", false); TreeNode SZCell2 = new TreeNode("SZCell2", "Cell", false); TreeNode NJCell2 = new TreeNode("NJCell2", "Cell", true); List<TreeNode> AllGroup = new ArrayList<TreeNode>(); AllGroup.add(GD); AllGroup.add(JS); All.setChildren(AllGroup); List<TreeNode> GDGroup = new ArrayList<TreeNode>(); GDGroup.add(SZ); GD.setChildren(GDGroup); List<TreeNode> JSGroup = new ArrayList<TreeNode>(); JSGroup.add(NJ); JS.setChildren(JSGroup); List<TreeNode> SZGroup = new ArrayList<TreeNode>(); SZGroup.add(SZBSC3); SZ.setChildren(SZGroup); List<TreeNode> NJGroup = new ArrayList<TreeNode>(); NJGroup.add(NJBSC1); NJGroup.add(NJBSC3); NJ.setChildren(NJGroup); List<TreeNode> SZBSC3Group = new ArrayList<TreeNode>(); SZBSC3Group.add(SZBTS1); SZBSC3Group.add(SZBTS3); SZBSC3.setChildren(SZBSC3Group); List<TreeNode> NJBSC3Group = new ArrayList<TreeNode>(); NJBSC3Group.add(NJBTS3); NJBSC3.setChildren(NJBSC3Group); List<TreeNode> SZBTS3Group = new ArrayList<TreeNode>(); SZBTS3Group.add(SZCell2); SZBTS3.setChildren(SZBTS3Group); List<TreeNode> NJBTS3Group = new ArrayList<TreeNode>(); NJBTS3Group.add(NJCell2); NJBTS3.setChildren(NJBTS3Group); System.out.println(new TreeSqlTranslator().parseTree2Sql(All)); } }