昨天看到一篇面試阿里的文章
面試分享:專科半年經驗面試阿里前端P6+總結(附面試真題及答案)
對其中一道筆試題產生了興趣,特意把思考過程付諸於文字。前端
1.實現destructuringArray方法,
達到以下效果
// destructuringArray( [1,[2,4],3], "[a,[b],c]" );
// result
// { a:1, b:2, c:3 }
複製代碼
原文做者給出了一個解法,實現方式比較巧妙,並且在臨場發揮的場景下,更加顯得可貴,感興趣的能夠去原文看一下。vue
如今拿到這個題,有充裕時間的去考慮。react
先上測試用例面試
[1,[2,4],3], "[a,[b],c]" ==> {a:1,b:2,c:3}
[1,[2,3,[4]]], "[react,[vue,rxjs,[angular]]]" ==> {react:1,vue:2,rxjs:3,angular:4}
[1,[2,4],3], "[a,[[b],c]" ==> Error: too many left square brackets
[1,[2,4],3], "[a,[b]],c]" ==> Error: too many right square brackets ==>pos:9
[1,[2,4],3], "[33a,[b],c]" ==> Error: key is invalid varible => 33a
[1,[2,4],3], "[re[a]ct,[b],c]" ==> Error: invalid string==>pos:3
[1,[2,4],3], "rx,[react,[b],c]" ==> Error: template with invalid start or end
[1,[2,4],3], "[react,[b],c],rx" ==> Error: template with invalid start or end
複製代碼
本文的目標就是要跑通以上的case.後端
我在探索這道題解法的過程當中,首先思考面試官考察的是什麼。不及格的是不會作或者給了一個錯誤的解法;及格的是對於給定的用例獲得正確結果;再好一點能對入參作基本的校驗;比較優秀的是能考慮絕大多數邊界狀況,正確的用例返回正確結果,錯誤的用例拋出相應異常。數組
繼而想到在作項目過程當中,不管是寫業務邏輯,抑或封裝公共基礎庫,不但須要能處理正常流程,更重要的是能處理可能遇到的異常。這樣作出來的東西纔會有更少的bug,畢竟bug是測試工程師走進一家酒吧要了-1杯啤酒引發的(笑)bash
而後筆者又發散思惟,想到書寫代碼過程或者在使用模板(如傳統的後端模板引擎或是前端jsx)的過程當中,若是用了不合法的書寫方式,總能獲得具體到位置的錯誤信息。感嘆寫優秀的庫和框架須要思考一萬種測試工程師走進酒吧的方式,才能變得流行。框架
扯遠了,回到這個題。咱們想用比較優秀的方式去解決這個題,就須要能考慮到這道題可能遇到的異常狀況,並給出具體的錯誤信息。post
下面分3步來處理:測試
一、解析模板字符串(例如"[a,[b],c]"),判斷是不是合法的數組字符串,若是不是的話給出具體的錯誤信息,若是是的話返回解析後的結果。
二、根據第一步拿到的解析結果,跟數組進行比對,若是結構不一致(好比[1,2] 對應 "[a,[b]]", b是在數組的數組裏,而2是個數字,沒辦法獲得b),拋出相應的異常。若是一致進入下一步。
三、根據數組和第一步獲得的解析結果輸出結果。
下面上代碼,先看第一步的代碼,這一步是最複雜的,須要處理數組字符串各類異常狀況,若是字符串合法須要拿到數組的解析結果。
思路是遍歷數組字符串,獲取對應的 '[' 在的位置,以及數組字符串裏對應的key的信息。以"[react,[vue,rxjs,[angular]]]"爲例。輸出的信息包括兩個, 一個是 數組字符串裏數組的位置信息數組 爲[[],[1],[1,2]](簡單來講,示例裏一共有三對方括號,根據方括號的深度獲得每對方括號的位置,好比'[angular]'這個方括號的位置就是在第一層的第一個位置,再查找第二個位置,記錄爲[1,2]。再深的層次類推),另外一個是key的位置信息,返回一個keyMap,這裏
keyMap = {
react:[0],
vue:[1,0],
rxjs:[1,1],
angular:[1,2,0]
}
複製代碼
獲得key的位置信息以後咱們就可使用真實arr 直接獲取key的值了,好比 angular的能夠這樣獲取:arr[1][2][0]。
因此如今的關鍵和難點在於如何獲取這些信息。這裏筆者用的思路是這樣的:
遍歷數組字符串,
只處理4種狀況,左方括號([) 右方括號(]) 逗號(,) 和其餘字符。
定義一個指針index=0和一個key=""兩個局部變量(index用來記錄當前位置,與遍歷時for循環裏的i不同,這裏index是用逗號分隔的每一個變量的位置),
(1)若是是普通字符char,key+=char,用來拼寫key
(2)若是是逗號(,) 說明變量key已經拼接完成,位置index也要加1,把key push進keyList 裏存起來
(3)若是是左方括號([),說明要步入一個新的數組,
此時把當前的位置序號index push進indexList裏存起來,並把index置爲0,由於新數組的序號又從0開始了。
再把 indexList push進arrayPosList,表明當前新數組在的位置。
(4)若是是右方括號(]),說明要步出當前數組, 此時有兩步操做,1 是把 indexList裏最後一個元素(步入當前數組時的位置)pop出來,賦值給index。 2 是key拼接完成,push進keyList(這裏還要多一步,頂部聲明一個keyMap={}對象,存儲key的位置信息,keyMap[key] = [...indexList, index];indexList是當前數組的位置信息,加上index是key在當前數組的位置,合一塊兒就是key在整個數組字符串裏的位置信息。括號裏這一步也應該加到第(2)小步)
舉個例子
在"[react,[vue,rxjs,[angular]]]"中,當遇到第一個字符‘[’時,indexList.push(index),indexList=[0]; 當遇到‘,’時,index加了1,再遇到第二個‘[’時,indexList.push(index),indexList=[0,1],把index置爲0,再日後查找到angular在的數組方括號時,此時index經過加加操做變爲了2,調用indexList.push(index),此時indexList 變爲[0,1,2]。這樣數組字符串裏3個方括號的位置信息就都有了
function parse(template) {
let indexList = [],
arrayPosList = [],
data = {},
keyList = [],
key = '',
index = 0,
keyMap = {},
spaceReg = /\s/g;
//去除字符串裏的空格
template = template.replace(spaceReg, "");
let len = template.length;
for (let i = 0; i < len; i++) {
let char = template[i];
if (char == '[') {
indexList.push(index);
index = 0;
arrayPosList.push(indexList.slice(1)) //indexList裏邊第0個元素0,是第一個方括號的位置, 是多餘的,這裏從第1個開始
} else if (char == ']') {
if (key !== '') {
keyList.push(key);
keyMap[key] = [...indexList.slice(1), index]; //indexList裏邊第0個元素0,是第一個方括號的位置, 是多餘的,這裏從第1個開始
key = '';
}
index = indexList.pop();
} else if (char == ',') {
if (key !== '') {
keyList.push(key);
keyMap[key] = [...indexList.slice(1), index]; //indexList裏邊第0個元素0,是第一個方括號的位置, 是多餘的,這裏從第1個開始
key = '';
}
index++;
} else {
key += char;
}
prevChar = char;
}
console.log("arrayPosList==>",arrayPosList,"\nkeyMap==>", keyMap)
return [arrayPosList, keyMap];
}
複製代碼
到這裏正常的數組字符串已經能解析了,咱們試着調用一下parse("[react,[vue,rxjs,[angular]]]"),輸出以下
arrayPosList==> [ [], [ 1 ], [ 1, 2 ] ]
keyMap==> { react: [ 0 ],
vue: [ 1, 0 ],
rxjs: [ 1, 1 ],
angular: [ 1, 2, 0 ] }
複製代碼
已經能正確的解析出數組的結構信息和每一個變量對應的位置了。
可是,假如用戶的入參不符合標準的話,如 "[react,[vue]"(多了左方括號)、"[react,vue]]"(多了右方括號)、"[react,v[u]e]"(括號位置錯亂)、"[33react,@vue]]"(變量不合法)等諸多異常狀況都沒有處理,下面咱們給出處理異常狀況的代碼,註釋裏有解釋是處理什麼樣的異常
function parse(template) {
let indexList = [],
arrayPosList = [],
data = {},
keyList = [],
key = '',
index = 0,
keyMap = {},
spaceReg = /\s/g;
*********//去除空格 這一步已經丟失了字符最初的位置,還須要進行完善 *********
template = template.replace(spaceReg, "");
let len = template.length;
*********//處理異常case "js,[react,vue]" 或 "[react,vue],js"*********
if (template[0] !== '[' || template[len - 1] !== ']') {
throw new Error('template with invalid start or end')
}
for (let i = 0; i < len; i++) {
let char = template[i];
if (char == '[') {
let prevChar = template[i - 1];
*********//左方括號前邊必須是‘[’、‘,’或者undefined,undefined對應數組字符串第一個左方括號前邊的位置。 例如 "[r[eact],vue]"*********
if (prevChar !== undefined && prevChar !== ',' && prevChar != '[') {
throw new Error('invalid string==>pos:'+i)
}
indexList.push(index);
index = 0;
arrayPosList.push(indexList.slice(1)) //indexList裏邊第0個元素0,是第一個方括號的位置, 是多餘的,這裏從第1個開始
} else if (char == ']') {
let nextChar = template[i + 1];
*********//右方括號後邊必須是‘]’、‘,’或者undefined,undefined對應數組字符串最後一個右方括號後邊的位置。 例如 "[react,[vu]e]"*********
if (nextChar !== undefined && nextChar !== ',' && nextChar != ']') {
throw new Error('invalid string==>pos:'+i)
}
if (key !== '') {
keyList.push(key);
keyMap[key] = [...indexList.slice(1), index]; //indexList裏邊第0個元素0,是第一個方括號的位置, 是多餘的,這裏從第1個開始
key = '';
}
index = indexList.pop();
*********//若是index是undefined,說明indexList空了,說明右方括號比左方括號多,結構不對*********
if (index === undefined) {
throw new Error('too many right square brackets ==>pos:' + i)
}
} else if (char == ',') {
if (key !== '') {
keyList.push(key);
keyMap[key] = [...indexList.slice(1), index]; //indexList裏邊第0個元素0,是第一個方括號的位置, 是多餘的,這裏從第1個開始
key = '';
}
index++;
} else {
key += char;
}
}
*********//若是indexList 還有元素,說明左方括號比右方括號多*********
if (indexList.length > 0) {
throw new Error('too many left square brackets')
}
*********//檢查js變量合法性的正則*********
let reg = /^(_|\$|[A-Za-z])(_|\$|\w)*$/
keyList.forEach(key => {
if (!reg.test(key)) {
throw new Error('key is invalid varible => ' + key)
}
})
console.log("arrayPosList==>",arrayPosList,"\nkeyMap==>", keyMap)
return [arrayPosList, keyMap];
}
複製代碼
處理異常的邏輯比較簡單,難點在因而否能考慮到全部的異常case並進行相應處理。
接下來的事情就比較簡單了,拿到數組字符串的結構和全部key的位置信息,咱們就能夠很輕鬆的拿到key對應真實數組的value了。
接下來先把真實數組arr和數組字符串的結構進行比對,邏輯比較簡單,下面直接給出代碼
// arr=[1,[2,3,[4]]] template = "[react,[vue,rxjs,[angular]]]" ==parse(template)==> arrayPosList = [ [], [ 1 ], [ 1, 2 ] ]
function check(arr, arrayPosList) {
遍歷arrayPosList,對數組字符串中每一個數組的位置信息,查看arr裏對應的位置是不是數組,若是有一個檢查不經過,說明arr與數組字符串的結構對應不上。好比這裏的arr=[1,[2,3,4]], 4和[angular] 對應,這時angular沒法解析了,這裏會拋出異常
arrayPosList.forEach(item => {
if (item.length == 0) return;
let ret = arr;
try {
for (let i = 0; i < item.length; i++) {
ret = ret[item[i]]
}
} catch (e) {
throw new Error('invalid structure');
return;
}
if (!Array.isArray(ret)) {
throw new Error('invalid structure');
}
})
}
複製代碼
最後一步,就是根據keyMap從數組裏取得對應的value了。通過前面的parse和check步驟,到了這一步說明數組和數組字符串是可以進行解析的。
function destructuringArray(arr, template) {
//必要的校驗
if (!Array.isArray(arr)) {
throw new Error('invalid first argument');
}
//必要的校驗
if (typeof template !== 'string') {
throw new Error('invalid second argument');
}
//最終返回結果
let ret = {};
//解析數組字符串
let [arrayPosList, keyMap] = parse(template);
//檢查數組和字符串的結構是否match
check(arr, arrayPosList);
//遍歷keyMap,取到每個key對應的value
Object.keys(keyMap).forEach(key => {
let path = keyMap[key];
let val = arr;
for (let i = 0; i < path.length; i++) {
val = val[path[i]]
}
ret[key] = val
})
//輸出
console.log(ret, '========')
return ret;
}
複製代碼
作完這道題,筆者又發散了一下思惟,假如字符串須要解析‘{’和‘}’, 又能處理 null,undefined,true,false,數字的值,複雜度上升了一個數量級。不由感嘆本身和大神之間的差距以光年計 。
本文完