在我以前的項目中,曾經遇到過這樣一個需求,編寫一個級聯選擇器,大概是這樣:前端
圖中的示例使用的是Ant-Design的Cascader組件。面試
要實現這一功能,我須要相似這樣的數據結構:算法
var data = [{ "value": "浙江", "children": [{ "value": "杭州", "children": [{ "value": "西湖" }] }] }, { "value": "四川", "children": [{ "value": "成都", "children": [{ "value": "錦裏" }, { "value": "方所" }] }, { "value": "阿壩", "children": [{ "value": "九寨溝" }] }] }]
一個具備層級結構的數據,實現這個功能很是容易,由於這個結構和組件的結構是一致的,遞歸遍歷就能夠了。數據庫
可是,因爲後端一般採用的是關係型數據庫,因此返回的數據一般會是這個樣子:編程
var data = [{ "province": "浙江", "city": "杭州", "name": "西湖" }, { "province": "四川", "city": "成都", "name": "錦裏" }, { "province": "四川", "city": "成都", "name": "方所" }, { "province": "四川", "city": "阿壩", "name": "九寨溝" }]
前端這邊想要將數據轉換一下其實也不難,由於要合併重複項,能夠參考數據去重的方法來作,因而我寫了這樣一個版本。windows
'use strict' /** * 將一個沒有層級的扁平對象,轉換爲樹形結構({value, children})結構的對象 * @param {array} tableData - 一個由對象構成的數組,裏面的對象都是扁平的 * @param {array} route - 一個由字符串構成的數組,字符串爲前一數組中對象的key,最終 * 輸出的對象層級順序爲keys中字符串key的順序 * @return {array} 保存具備樹形結構的對象 */ var transObject = function(tableData, keys) { let hashTable = {}, res = [] for( let i = 0; i < tableData.length; i++ ) { if(!hashTable[tableData[i][keys[0]]]) { let len = res.push({ value: tableData[i][keys[0]], children: [] }) // 在這裏要保存key對應的數組序號,否則還要涉及到查找 hashTable[tableData[i][keys[0]]] = { $$pos: len - 1 } } if(!hashTable[tableData[i][keys[0]]][tableData[i][keys[1]]]) { let len = res[hashTable[tableData[i][keys[0]]].$$pos].children.push({ value: tableData[i][keys[1]], children: [] }) hashTable[tableData[i][keys[0]]][tableData[i][keys[1]]] = { $$pos: len - 1 } } res[hashTable[tableData[i][keys[0]]].$$pos].children[hashTable[tableData[i][keys[0]]][tableData[i][keys[1]]].$$pos].children.push({ value: tableData[i][keys[2]] }) } return res } var data = [{ "province": "浙江", "city": "杭州", "name": "西湖" }, { "province": "四川", "city": "成都", "name": "錦裏" }, { "province": "四川", "city": "成都", "name": "方所" }, { "province": "四川", "city": "阿壩", "name": "九寨溝" }] var keys = ['province', 'city', 'name'] console.log(transObject(data, keys))
還好keys的長度只有3,這種東西長了根本沒辦法寫,很明顯能夠看出來這裏面有重複的部分,能夠經過循環搞定,可是想了好久都沒有思路,就擱置了。後端
後來,有一天晚飯後不是很忙,就跟旁邊作數據的同事聊了一下這個需求,請教一下該怎麼用循環來處理。他看了一下,就問我:「你知道trie樹嗎?」。我頭一次聽到這個概念,他簡單的給我講了一下,而後說感受處理的問題有些相似,讓我能夠研究一下trie樹的原理並試着優化一下。數組
講道理,trie樹這個數據結構網上確實有不少資料,但不多有使用JavaScript實現的,不過原理卻是不難。嘗試以後,我就將transObject
的代碼優化成了這樣。(關於trie樹,還請讀者本身閱讀相關材料)數據結構
var transObject = function(tableData, keys) { let hashTable = {}, res = [] for (let i = 0; i < tableData.length; i++) { let arr = res, cur = hashTable for (let j = 0; j < keys.length; j++) { let key = keys[j], filed = tableData[i][key] if (!cur[filed]) { let pusher = { value: filed }, tmp if (j !== (keys.length - 1)) { tmp = [] pusher.children = tmp } cur[filed] = { $$pos: arr.push(pusher) - 1 } cur = cur[filed] arr = tmp } else { cur = cur[filed] arr = arr[cur.$$pos].children } } } return res }
這樣,解決方案就和keys的長短無關了。svg
這大概是我第一次,真正將數據結構的知識和前端項目需求結合在一塊兒。
目前爲止我參加過幾回前端開發方面的面試,確實有很多面試官會問道一些算法。一般會涉及的,是鏈表、樹、字符串、數組相關的知識。前端面試對算法要求不高,彷佛已是業內的一種共識了。雖然說算法好的前端面試確定會加分,可是僅憑常見的面試題,而不去聯繫需求,很難讓人以爲,算法對於前端真的很重要。
直到有一天,有一位面試官問我這樣一個問題,下面我按照本身的回憶把對話模擬出來,A指面試官,B指我:
A:你有寫過瀑布流嗎?
B:我寫過等寬瀑布流。實現是當用戶拉到底部的必定高度的時候,向後端請求必定數量的圖片,而後再插入到頁面中。
A:那我問一下,如何讓幾列圖片之間的高度差最小?
B:這個須要後端發來的數據裏面有圖片的高度,而後我就能夠看當前高度最小的是哪裏列,將新圖片插入那一列,而後再看看新的高度最小的是哪一列。
A:我以爲你沒有理解個人問題,個人意思是如何給後端發來的圖片排序,讓幾列圖片之間的高度差最小?
B:(想了一段時間)對不起,這個問題我沒有思路。
A:你是軟件工程專業的對吧?大家數據結構課有沒有學動態規劃?
B:可能有講吧,可是我沒什麼印象了。
對話大概就是這樣,雖然面試最終仍是pass了,但這個問題確實讓我很在乎,由於我以爲,高度差「最」小,真的能用很簡單的算法就解決嗎?
這個問題的實質,其實就是有一個數組,將數組元素分紅n份,每份全部元素求和,如何使每份的和的差最小。
搜索上面這個問題,很快就能找到相關的解答,很基本的一類動態規劃問題——揹包問題。
以前我確實看過揹包問題的相關概念(也僅僅是相關概念)。當時我看到這樣一段話:
許多使用遞歸去解決的編程問題,能夠重寫爲使用動態規劃的技巧去解決。動態規劃方案一般會使用一個數組來創建一張表,用於存放被分解成衆多子問題的解。當算法執行完畢,最終的解將會在這個表中很明顯的地方被找到。
後面是一個用動態規劃重寫斐波那契數列的例子。我看到它只是將遞歸的結果,保存在了一個數組中,就天真的覺得動態規劃是優化遞歸的一種方法,並無深刻去理解。
不求甚解,確實遲早會出問題的。當時我雖然覺得本身知道了算法的重要性,但其實仍是太年輕。
動態規劃能夠求解一類「最優解」問題,這在某種程度上讓我耳目一新。因爲本文主要仍是爲了說明數據結構與算法對於前端的意義,關於動態規劃的細節,本文也不會涉及,並且水平確實也不夠。網上有許多很是好的博文,尤爲推薦《揹包九講》。
將以下扁平對象,轉爲樹形對象。parent
字段爲空字符串的節點爲根節點:
var input = { h3: { parent: 'h2', name: '副總經理(市場)' }, h1: { parent: 'h0', name: '公司機構' }, h7: { parent: 'h6', name: '副總經理(總務)' }, h4: { parent: 'h3', name: '銷售經理' }, h2: { parent: 'h1', name: '總經理' }, h8: { parent: 'h0', name: '財務總監' }, h6: { parent: 'h4', name: '倉管總監' }, h5: { parent: 'h4', name: '銷售表明' }, h0: { parent: '', name: 'root' } };
這個需求在前端其實也很實際,示例中的對象是一個公司組織結構圖。若是需求是讓你在前端用svg之類的技術畫出這樣一張圖,就須要這個功能。(另外我想到的一種應用場景,就是在前端展現相似windows資源管理器的文件樹)
我當時想了好久,沒有想到一個循環解決的方法,後來在stackoverflow上找到了答案:
var plain2Tree = function (obj) { var key, res for(key in obj) { var parent = obj[key].parent if(parent === '') { res = obj[key] } else { obj[parent][key] = obj[key] } } return res }
這段代碼,就是利用了JavaScript裏面的引用類型,以後的思路,和操做指針沒什麼區別,就是構造一棵樹。
但對於我來講,歷來都沒有往樹和指針的那方面思考,就很被動了。
以上列舉了三道題,但願能夠引發你們對於在前端應用數據結構與算法相關知識的共鳴。