天天看點

過濾/篩選樹節點

又是樹,是我跟樹杠上了嗎?—— 不,是樹的問題太多了! 🔗 相關文章推薦: 使用遞歸周遊并轉換樹形資料(以 typescript 為例) 從清單生成樹 (javascript/typescript)

過濾和篩選是一個意思,都是 filter。

對于清單來說,過濾就是丢掉不需要的,留下需要的。但對于樹來說就得分情況了。

如果想“過濾掉”(丢掉)某些節點,會把它的子節點一并抛棄,就像砍樹枝一樣,幹之不存,枝将焉附?這種情況多是去除不需要的子樹。

如果是想“查找”某些節點,會将找到的節點及其上溯到根的所有節點都保留下來。對于找到的節點,除了保留其完整路徑之外,對其子樹還有兩種處理方式:

一種是“到此為止”,也就是說,如果其子樹中沒有符合條件的節點,那就不需要了,砍掉。需要定位到符合條件的節點以便後繼操作是采用這種方式,這也是最常用的查找方式。

另一種是保留其完整子樹。如果需要使用符合條件節點的子節點(比如選擇指定部門及其子部門)會采用這種方式。

過濾和查找的主要差別在于:“過濾”通常會遇到不符合保留條件(或符合剔除條件)的節點就直接砍掉,不管其子樹中是否還存在符合保留條件的節點;而查找則會一直找到葉節點上,隻有整條路徑都沒有符合保留條件的節點,才會從其某個祖先節點上砍掉(祖先節點是否保留取決于其下是否存在符合保留條件的子孫節點)。

下面一樣一樣來。示例代碼使用 typescript 編寫,示例資料來源于從清單生成樹 (javascript/typescript) 一文,同時使用該文中定義的節點類型接口:

過濾掉不需要的節點,思路比較簡單:

周遊目前節點的所有子節點,需要的留,不需要的删

對留下的節點,通過遞歸進行過濾

按此思路,typescript 代碼是

如果對示例資料(見前文)進行過濾,僅保留 <code>id</code> 是偶數的節點,那結果是

過濾/篩選樹節點

不過這個 <code>filtertree</code> 有點小瑕疵:

遞歸調用時還需要傳入 <code>predicate</code>,有點繁瑣

傳入參數應該限制在 <code>treenode[]</code> 類型上,添加 <code>undefined</code> 隻是為了簡化遞歸調用(不用先判空)

處理起來也簡單,加一層接口封裝一下(門面模式):

實際使用的時候,可能傳入的可能是單根樹 (<code>treenode</code>),也有可能是多根 (<code>treenode[]</code>),那可以寫個重載:

查找節點就要稍微複雜了點了,因為需要保留路徑。判斷目前節點是否可以删除需要對自己情況進行判斷之外,還取決于其所有子孫節點是否可以删除。與前面“過濾掉”的邏輯相比,有兩點變化:

不管目前節點是否保留,均需要遞歸向下,把子孫中符合條件的節點都找出來

隻要子孫中存在符合條件的節點,目前節點就應該保留。

這樣處理後的節點,所有葉節點都應該符合查找條件。比如在示例資料中按 <code>id</code> 參整除 <code>6</code> 來查找節點,結果是:

過濾/篩選樹節點

根據上面的邏輯,寫一個 <code>findtreenode()</code>:

下面把代碼修改下,在結果中保留子樹。

這個思路跟最上面那個“剔除”的思路正好相反,

遇到符合條件的節點,直接保留整棵子樹,也不需要遞歸去處理了

不符合條件的節點,遞歸進去繼續找

既然都是查找,可以給 <code>findtreenode()</code> 添加一個 <code>keepsubtree: boolean</code> 參數來擴充函數功能。接口部分改變如下:

然後需要修改的地方主要是 <code>array.prototype.filter</code> 回調函數,可以先把原來的箭頭函數提取出來,命名為 <code>filterwithoutsubtree()</code>。

過濾/篩選樹節點

然後再寫一個 <code>filterwithsubtree()</code> 處理函數。根據 <code>keepsubtree</code> 的值來決定使用哪一個過濾器。關鍵代碼如下:

含完整子樹的查找結果示例(查找條件:<code>it =&amp;gt; it.id % 4 === 0</code>)如下圖:

過濾/篩選樹節點

繼續閱讀