關於一道前端筆試題的思考

昨天看到一篇面試阿里的文章
面試分享:專科半年經驗面試阿里前端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,數字的值,複雜度上升了一個數量級。不由感嘆本身和大神之間的差距以光年計 。

本文完

相關文章
相關標籤/搜索