這是我參與更文挑戰的第4天,活動詳情查看: 更文挑戰javascript
那是一個月黑風高,伸手不見五指的(並非由於我黑)的夜晚,個人小夥伴給我發了一道算法題,題目以下。html
將題中的字符串text解析爲一個標題樹。java
const text = ` - 章節一 - 標題一 - 標題二 - 小節四 - 章節二 - 標題一 - 標題二 - 標題三 - 子標題四 - 小節五 `;
class Node {
constructor({ value, level }) {
this.value = value;
this.level = level;
this.children = [];
// hint: 也可在數據結構中增長 this.parent 節點輔助解析
}
}
const tree = parseTree(text);
// [
// Node { value: "章節一", children: [ Node, Node ], level: 1 },
// Node { value: "章節二", children: [ Node, Node ], level: 1 }
// ]
function parseTree(text){
// code
}
複製代碼
在細細思索以後,砸吧砸吧嘴,這會不會我小夥伴釣魚,他本身寫不出來因此讓我來幫他寫?算法
這玩意和掘金的文章閱讀頁裏,右側的目錄不能說是很像,只能說是如出一轍。瀏覽器
雖然有段時間沒摸算法了,可是我憑直覺認爲,這道題,我ok的。markdown
準備工做其實沒啥,先建立好一個html文件,而後把上面的基礎代碼複製過來,而後在瀏覽器打開下。數據結構
而後走到廚房,拿起左手第三個罐子,從裏面掏出一包黑色的,新到的茶包,泡一杯熱茶,反正接下來一段時間也睡不着, 那就讓本身更清醒點。post
嘻嘻嘻,皮一下,該有的準備工做仍是要的, 我這該死的儀式感。ui
最簡單的實現思路很簡單,就是肉眼可見的兩個空格爲一個level, 即沒有雙空格前綴是level 1,一個雙空格,就是level 2, 以此類推。this
version 1.0
function parseTree(text){
let parent = new Node({value: '', level: 0}); // 建立一個根節點,方便更新父節點
let level = 1; // 用於後續標題的層級遞進
let flag = ' '; // 層級標識,默認是兩個空格
for(let item of text.split('\n')){ // 先將字符串按換行切割,用於遍歷
if(!item) continue // 若是當前項爲空,直接跳過
// 遞歸肯定當前項具體在第幾層
// 每次遞歸處理的都是確認當前項的層級,
// 因此不用小心斷層會識別不了狀況。
checkFlag(item, level, parent, flag)
}
/** * @params item 當前須要處理的項 * @params level 當前項的默認level * @params parent 當前項的父節點 * @params flag 用於計算當前項的level */
function checkFlag(item, level, parent, falg){
if(item.indexOf(flag) !== -1){ // 若是當前項存在雙空格前綴,標識不是當前level
let len = parent.children.length;
//從新確認父級 若是當前父級存在子節點,那麼將本身設爲新父級
parent = len ? parent.children[length - 1] : parent;
// 這裏注意,無論是否更新父級,當前層級須要level + 1
checkFlag(item.substr(flag.length, item.length), level + 1, parent, flag)
} else {
// 這裏的item.substr是爲了祛除字符串中的'- '前綴
parent.children.push(new Node({value: item.subnstr(2, item.length), level: level}))
}
}
return parent.children;
}
複製代碼
其實這裏對當時的場景作了下PS, 我並無直接去思考固定標誌爲雙空格的狀況,由於考慮到前綴是有可能變化的,我想一步到位,寫一個能自動識別標題前綴的,因此當時着實糾結了一番。
還和朋友吵了一段時間, 他想讓我直接按雙空格我實現,我偏不, 我以爲那樣太簡單了,我想加個level, 如下是我直接實現的,邏輯部分和上面的代碼一致,只是加了識別前綴的部分。
首先,第一步,改造默認的標題字符串
這裏假設應用於掘金的文章Markdown解析,顯不出力內容節點,只看標題節點,大體以下
const text = ` #- 章節一 ##- 標題一 ##- 標題二 ###- 小節四 #- 章節二 ##- 標題一 ##- 標題二 ##- 標題三 ###- 子標題四 ####- 小節五 `;
複製代碼
那麼這時候他的標題標識符就變了,固然在上面的代碼裏,修改flag的默認值就能解決了,可是這不是我想要的。
我想讓他本身識別,因此我但願在他碰到第一個標題時,去自動獲取標題的標識符。
function parseTree(text){
let parent = new Node({value: '', level: 0}); // 建立一個根節點,方便更新父節點
let level = 1; // 用於後續標題的層級遞進
let flag = null; // 層級標識,默認爲空
for(let item of text.split('\n')){ // 先將字符串按換行切割,用於遍歷
if(!item) continue // 若是當前項爲空,直接跳過
// 遇到首個非一級標題時,確認段落標識
if(!flag && item.indexOf('- ') !== 0){
flag = item.substr(0, item.indexOf('- '))
}
// 調用遞歸用於確認層級
checkFlag(item, level, parent, flag)
}
/** * @params item 當前須要處理的項 * @params level 當前項的默認level * @params parent 當前項的父節點 * @params flag 用於計算當前項的level */
function checkFlag(item, level, parent, falg){
if(item.indexOf(flag) !== -1){ // 若是當前項存在雙空格前綴,標識不是當前level
let len = parent.children.length;
//從新確認父級 若是當前父級存在子節點,那麼將本身設爲新父級
parent = len ? parent.children[length - 1] : parent;
// 這裏注意,無論是否更新父級,當前層級須要level + 1
checkFlag(item.substr(flag.length, item.length), level + 1, parent, flag)
} else {
// 這裏的item.substr是爲了祛除字符串中的'- '前綴
parent.children.push(new Node({value: item.subnstr(2, item.length), level: level}))
}
}
return parent.children;
}
複製代碼