扁平樹狀數據處理及多層關鍵字搜索實現

1、問題背景及需求

1.需求

  • 實現多層關鍵字模糊搜索;
  • 搜索整顆樹節點,包括非子節點和子節點,命中時自動展開其父節點;
  • 非子節點命中時,其子節點所有保留;
  • 節點命中關鍵字高亮。

[搜索前樹狀展現] 後端

[搜索關鍵字"測試"] 數組

2.存在問題

  • 後端返回的數據爲扁平化數據,展現須要轉成樹狀結構。
  • 數據量大,共4000+數據,爲性能考慮,實現搜索功能時須要減小遍歷次數。

2、實現原理

1.扁平數據轉化樹狀結構實現

原理

  • 將散亂扁平化的節點構形成以id爲索引的對象據

  • 遍歷扁平化節點,逐一尋找其父節點,並插入。bash

    同時判斷是否根節點,如果直接抽出根節點。數據結構

代碼實現

須要遍歷兩次數據:構造以id爲索引的對象、尋找父節點據性能

(根節點parent === '0')測試

/**
 * 扁平結構轉成樹狀結構
 * @param category 扁平數據
 */
formateTree = (category) => {
  const treeMap = {} // 以id爲索引的對象
  const tree = [] // 最後樹狀結果
  
  category.forEach((o) => {
    treeMap[o.id] = Object.assign(o, { children: [] }
  })
  
  category.forEach((o) => {
    if (o.parent !== '0') {
      treeMap[o.parent].children.push(treeMap[o.id])
    } else if (o.parent === '0') {
      tree.push(treeMap[o.id])
    }
  })
  return tree
}
複製代碼

2.實現多層關鍵字模糊搜索

準備

  • 樹狀結構
  • 以id爲索引的對象
  • 存放各路徑下第一個命中節點的數組 filterNode[]
  • 存放最終結果的數組 treeData[]

原理

1.【遞歸】根據關鍵字遍歷樹狀結構尋找各路徑下第一命中節點 findTargetNode() ,並插入filterNode[],將節點parent 插入expandedList[]ui

命中標記:⭐spa

2.【遞歸】展開非子節點下包含關鍵字節點的路徑 findExpandedNode() ,將命中節點parent 插入expandedLish[]3d

⚠️ 注意,如中間有兩層以上沒有命中關鍵字,則沒法展開,最後須要根據expandedList[] 中的節點向上補齊 insertNodeParent() code

3.【遞歸】尋找命中節點的父節點,拼接成樹 findParend()

4.expandedList[] 去重。

【遞歸】根據filterNode[] 中的節點向上補齊已展開節點的各級父節點,用到索引對象 insertNodeParent()

5.原理同3

6.expandedList[] 去重。

代碼實現

  • findTargetNode()

    第一命中節點爲末節點時:插入(將該節點push 進filterNode[] ),展開(並將其父id push 到expandedList[] )。

    第一命中節點爲非子節點時:插入,並須要遍歷該路徑繼續尋找剩餘命中節點 findExpandedNode()

const findTargetNode = (val, tree, filterNode, expandedList) => {
  tree.forEach((item) => {
    if (item.title.indexOf(val) > -1) {
      if (!item.children.length) {
        filterNode.push(item);
        expandedList.push(item.parent);
      } else {
        filterNode.push(item);
        expandedList.push(item.parent);
        findExpandedNode(val, tree, expandedList);
      }
    } else {
      findTargetNode(val, item.children, filterNode, expandedList);
    }
  })
}
複製代碼

  • findExpandedNode()

    末節點包含關鍵字:展開;不包含:不處理。

    非末節點包含關鍵字:展開,繼續遞歸;不包含:繼續遞歸。

const findExpandedNode = (val, tree, expandedList) => {
  tree.forEach((item) => {
    if (!item.children.length && item.title.indexOf(val) > -1) {
      expandedList.push(item.parent);
    } else if (item.children.length && item.title.indexOf(val) > -1) { 
      expandedList.push(item.parent);
      findExpandedNode(val, item.children, expandedList)
    } else if (item.children.length && item.title.indexOf(val) === -1) {
      findExpandedNode(val, item.children, expandedList)
    }
  })
}
複製代碼

  • findParend()

    父節點不是根節點

    目前對象樹中父節點是否已包含該節點,包含:不梳理;未包含:插入父節點。(避免重複)
    複製代碼

    父節點爲根節點

    目前結果中,是否已包含該根節點,包含:不處理;未包含:插入結果中。 
    複製代碼
const findParend = (item, treeMap, treeData, expandedList) => {
  if (item.parent !== '0') {
    const ids = treeMap[item.parent].children.map(o => o.id);
    if (!ids.includes(item.id)) {
      treeMap[item.parent].children.push(item);
      expandedList.push(item.parent);
      findParend(treeMap[item.parent], treeMap, treeData, expandedList);
    }
  } else {
    const ids = treeData.map(o => o.id);
    if (!ids.includes(item.id)) {
      treeData.push(item);
      expandedList.push(item.id)
    }
  }
}
複製代碼

  • expandedList[] 去重

    Es6 set數據結構

expandedList = [...new Set(expandedList)]
複製代碼

  • insertNodeParent()

    當前節點爲非根節點時,插入該節點父id

    當前節點爲根節點時,返回

const insertNodeParent = (key, expandedList, treeMap) => {
  if (key === '0') {
    return 
  }
  const { parent } = treeMap[key]
  expandedList.push(parent)
  if (parent !== '0') {
    insertNodeParent(parent, expandedList, treeMap)
  }
}
複製代碼
相關文章
相關標籤/搜索