關於樹形插件展現中數據結構轉換的算法

問題背景

在一些目錄結構、機構層級等展現的場景中,咱們常常會用到一些成熟的樹形插件來進行輕鬆展現,好比ztree等。大多數插件會支持對兩種數據源格式的解析,一種是通用的二維數據結構,一種是樹狀數據結構。對於這兩種數據結構的稱呼在各插件中可能不盡相同,這裏依照二維結構和樹狀結構來稱呼。舉例說明以下:javascript

// 二維數據結構
[{
  "id": "001",
  "name": "總部",
  "parentId": "0"
}, {
  "id": "002",
  "name": "二級門店1",
  "parentId": "001"
}, {
  "id": "003",
  "name": "三級門店",
  "parentId": "002"
}, {
  "id": "004",
  "name": "二級門店2",
  "parentId": "001"
}]

// 樹狀數據結構
[{
    "id": "001",
    "name": "總部",
    "parentId": "0",
    "children": [{
      "id": "002",
      "name": "二級門店1",
      "parentId": "001",
      "children": [{
        "id": "003",
        "name": "三級門店",
        "parentId": "002",
        "children": []
      }]
    }, {
      "id": "004",
      "name": "二級門店2",
      "parentId": "001",
      "children": []
    }]
}]

但在某些插件中,或在某些特殊場景中,咱們有兩種數據結構之間相互轉換的需求,須要本身寫一個輔助函數來完成。這裏就提供兩個這樣的工具函數來完成數據結構的轉換。java

Note: 要說明的是,工具函數沒有通過大數據量轉換測試,因此對有實時性、大量源數據轉換需求的同窗而言,請自行測試分析,可採起前置或異步等方案處理。因爲自身技術水平的侷限性,算法自己會有性能優化的空間,如有更優處理算法,還望交流分享,謝謝!node

解決方案

咱們來分開介紹兩種數據結構之間的轉換算法,每一個小結中我會先貼出整個函數的代碼清單,以便你們複製粘貼,而後會簡要說明其中大概的邏輯思路。算法

二維數據結構 => 樹狀數據結構

/**
 * 將通用的二維數據結構轉換爲樹狀數據結構
 * @param  {String} rootParentIdValue 表示根節點的父類id值
 * @param  {String} parentIdName      表示父類id的節點名稱
 * @param  {String} nodeIdName        表示二維結構中,每一個對象主鍵的名稱
 * @param  {Array} listData           爲二維結構的數據
 * @return {Array}                    轉換後的tree結構數據
 */
function listToTree(rootParentIdValue, parentIdName, nodeIdName, listData) {
  if (listData instanceof Array && listData.length > 0 && listData[0][parentIdName]) {
    var rootList = [],
        nodeList = []
      
    listData.forEach(function(node, index) {
      if (node[parentIdName] == rootParentIdValue) {
        rootList.push(node);
      } else {
        nodeList.push(node);
      }
    });

    if (nodeList.length > 0 && rootList.length > 0) {
      childrenNodeAdd(rootList, nodeList);
      return rootList;
    } else if (rootList.length > 0) {
      throw new Error("沒有對應的子節點集合");
    } else {
      throw new Error("沒有對應的父類節點集合");
    }

    function childrenNodeAdd(rootNodeList, childrenList) {
      if (childrenList.length > 0) { 
        rootNodeList.forEach(function(rootNode) {
          rootNode["children"] = [];
          var childrenNodeList = childrenList.slice(0); 
          childrenList.forEach(function(childrenNode, childrenIndex) {
            if (parentIdName in childrenNode && rootNode[nodeIdName] == childrenNode[parentIdName]) {
              rootNode["children"].push(childrenNode);
              childrenNodeList.splice(childrenIndex, 1);
            }
          });
          childrenNodeAdd(rootNode["children"], childrenNodeList);
        });
      }
    }
    
  } else {
    throw new Error("格式不正確,沒法轉換");
  }
}

此函數可經過listToTree("0", "parentId", "id", sourceData)調用測試,sourceData爲文章開頭給出的二維數據結構舉例。數組

下面簡要介紹一下其中邏輯,第10行是簡要驗證一下入參數據的合法性,而後聲明瞭rootList和nodeList兩個變量。其中rootList爲頂級根節點,nodeList爲其餘子節點集合,第14行到20行的循環即是爲兩個變量賦值,以後根據兩個變量的值進一步判斷數據的合法性。在驗證以後調用childrenNodeAdd這個內部函數,此函數以後將會被遞歸調用,爲每個節點添加指定名稱爲「children」的子節點數組。兩個入參分別是rootNodeList和childrenList,表明父節點集合,和其以後的全部子節點集合。在23行第一次調用時,傳入的即是頂級根節點和其以後的全部子孫節點。下面看這段帶有詳細註解的代碼片斷:性能優化

function childrenNodeAdd(rootNodeList, childrenList) {
      if (childrenList.length > 0) { //  若是沒有子節點了就結束遞歸
        //遍歷父節點集合,在子節點中查找其自身的子節點,並添加到對應的子節點數組中
        rootNodeList.forEach(function(rootNode) {
          rootNode["children"] = [];
          var childrenNodeList = childrenList.slice(0); //複製一個子節點數據,用於存放剩餘的子節點
          //遍歷全部子節點
          childrenList.forEach(function(childrenNode, childrenIndex) {
            if (parentIdName in childrenNode && rootNode[nodeIdName] == childrenNode[parentIdName]) { //根節點的id 等於子節點的父類id
              rootNode["children"].push(childrenNode); //添加對應節點歸爲子節點
              childrenNodeList.splice(childrenIndex, 1);//在剩餘子節點中剔除已經分配過的子節點
            }
          });
          childrenNodeAdd(rootNode["children"], childrenNodeList); //剩餘子節點繼續遞歸執行,每次遞歸一次就表示節點增長一級。
        });
      }
    }

樹狀數據結構 => 二維數據結構

/**
 * 將樹狀數據結構轉換爲二維數據結構
 * @param  {String} childrenName 樹狀結構中子節點名稱
 * @param  {Array} treeData     樹狀結構數據
 * @return {Array}              轉換後的通用二維結構數據
 */
function treeToList(childrenName, treeData) {
  var listData = [];
  transferTreeData(treeData);
  function transferTreeData (sourceData) {
     sourceData.forEach( function(node, nodeIndex) {
     if(node[childrenName].length > 0)
          transferTreeData(node[childrenName]);
      delete node[childrenName];
      listData.push(node);
     });
  }
  return listData;
}

此函數可經過treeToList("children", sourceData)調用測試,sourceData爲文章開頭給出的樹狀數據結構舉例。這裏的邏輯比較簡單就再也不贅述了。數據結構

相關文章
相關標籤/搜索