認真編寫的題目能測出程式碼水平,而有趣的題目則能激發人的“程式碼慾望”。
前言
前後參加過網易、美團、頭條、滴滴等公司的線上程式設計題。簡單的認為題目大體有以下三種型別:
- 認真編寫的題目會促進答題者去認真的回答
- 有趣的題目會激起答題者的興趣,以至於能激發一些『靈感』
- 沒有意義的題目不值得答,這是一個雙向選擇
筆試題在很多時候確實是個“坑”,能避開就避開,因為畢竟像狀況 2 的是少數。但優質的題目確實是一個很好的程式設計能力檢驗,遇到了就不要錯過。
2018 年春招的攜程前端筆試題是一個不錯的例子,下面對其中的程式設計題做一番分析。
P.S. 原文顯示效果更好喔:) check:rayjune.me/有趣的前端程式設計題
作者:RayJune(轉載請署名,請尊重博主含辛茹苦、遍查資料、一行一行含淚碼出來的成果)
常見問題
簡單列舉一下可能被問到的問題:
- 博主你的程式碼確定 100% AC 了嗎?
非常確定 100% AC。同時這意味著細節複雜度上的處理到位。(實際前端開發中要處理大量細節複雜度的東西,這同樣很有意思)
- 為啥不用 ES6 來寫呢?
避免有些筆試平臺不支援 ES6 的狀況,你懂的
- 為啥不用
new Set()
來去重呢?
同上,另外順便回顧一下陣列去重。在面試的時候寫 new Set
來陣列去重會被 diss 的,別問我為什麼知道 :>
- 為什麼博主你的程式碼沒有雜糅在一起,而是合理的抽出為多個函式呢?
一種程式設計品味。
程式設計風格
簡單陳述一下文中程式碼使用的程式設計風格:
- 使用
ES5
,以避免有些線上程式設計平臺不支援ES6
的狀況(所以在這裡沒有用new Set()
) - Airbnb 程式碼規範,不使用單
var
模式 - 變數定義(
var
),函式體(body
),return
值三者用空行隔開,邏輯鮮明 - 有意義的變數命名
- 適當的函式抽取,一個函式只做一件事情
另外還有 根據不同場合使用合適的型別判斷方式:
Array.isArray
判斷陣列,Object.prototype.toString.call
來判斷純物件typeof
判斷基本型別和function
instanceof
來判斷自定義的物件
一. 字串擷取
題目
描述
給定一個長度小於 50 且包含字母和數字的任意字串,要求按順序取出當中的數字和英文字母,數字需要去重,重新排列後的字串數字在前,字母在後。
輸入
需要擷取的字串(包含數字和字母)
輸出
按照要求重新排列的字串
樣例輸入
'攜程C2t0r1i8p2020校招'
複製程式碼
樣例輸出:
'2018Ctrip'
複製程式碼
解答
肯定有同學表示第一題不值得分析。但我還是想拋磚引玉一下,思路如下:
- 字串轉陣列:
str.split('')
(進而使用陣列的各種操作方法,如arr.forEach
) - 判斷字串值是否為數字:
/\d/.test(element)
或者Number.isNaN(Number(element)
- 判斷字串值是否為字母:
/[a-zA-Z]/.test(element)
- 數字字串去重(利用陣列去重)
- 輸出數字+字母
由此有了這一版程式碼:
條件 1~3
function handleStr(str) {
var arr = str.split('');
var nums = '';
var words = '';
arr.forEach(function (element) {
if (/\d/.test(element))) {
nums += element;
} else if (/[a-zA-Z]/.test(element) ) {
words += element;
}
});
return uniqueStr(nums) + words;
}
複製程式碼
去重
作為前端開發超高頻面試題,相信大家早已對陣列去重熟捻於心:
基本型別去重:
function unique(arr) {
return arr.filter(function (element, index) {
return arr.indexOf(element) === index;
});
}
複製程式碼
基本+複雜型別去重:
function unique(arr) {
var hash = {};
return arr.filter(function (element) {
if (hash.hasOwnProperty(element)) {
return false;
}
hash[element] = true;
return true;
});
}
複製程式碼
由於數字去重(str
,基本型別)基於陣列去重,我們要對原來的陣列去重做一點修改:
function uniqueStr(str) {
var arr = str.split('');
return arr.filter(function (element, index) {
return arr.indexOf(element) === index;
}).join('');
}
複製程式碼
string.split()
和 array.join()
幫助我們自由的遊走在字串和陣列間。
最終解答 1
function handleStr(str) {
var arr = str.split('');
var nums = '';
var words = '';
arr.forEach(function (element) {
if (/\d/.test(element)) {
nums += element;
} else if (/[a-zA-Z]/.test(element) ) {
words += element;
}
});
return uniqueStr(nums) + words;
}
function uniqueStr(str) {
var arr = str.split('');
return arr.filter(function (element, index) {
return arr.indexOf(element) === index;
}).join('');
}
// 測試
console.log(handleStr('攜程C2t0r1i8p2020校招'));
// 2018Ctrip
複製程式碼
最終解答 2
非常感謝評論區 @while大水逼 大神的寶貴建議,博主筆試的時候沒想到 str.match(regex)
的方法。實現更簡潔、邏輯的展示更好,在此補上:
function handleStr(str) {
var nums = str.match(/\d/g).join('');
var words = str.match(/[a-zA-Z]/g).join('');
return uniqueStr(nums) + words;
}
function uniqueStr(str) {
var arr = str.split('');
return arr.filter(function (element, index) {
return arr.indexOf(element) === index;
}).join('');
}
// 測試
console.log(handleStr('攜程C2t0r1i8p2020校招'));
// 2018Ctrip
複製程式碼
二. 陣列升維
題目
描述
對一維陣列,根據 type
型別分組成二維陣列
輸入
- 輸入的引數可能是空陣列
[]
,空物件null
,undefined
,數字,字串等異常值; - 也可能是結構為
[{ type, content}]
的有效值; - 甚至是
[null, null, (type, content)]
等有效和非法值混合的資料。
輸出
- 當輸入資料不合法時,輸出空陣列
[]
- 當輸入資料有效時(請先過濾陣列裡的異常元素),然後將相同
type
值的元素合併,形成新元素{"type": "A", "contents": [content1, content2]}
,其中,contents
為一個陣列,元素為所有 type 值相同的content
值。 - 注意,輸出的是一個標準
JSON
格式
樣例輸入
var input = [null, 2, "test", undefined, {
"type": "product",
"content": "product1"
}, {
"type": "product",
"content": "product2"
}, {
"type": "tag",
"content": "tag1"
}, {
"type": "product",
"content": "product3"
}, {
"type": "tag",
"content": "tag2"
}];
複製程式碼
樣例輸出
[{"type":"product","contents":["product1","product2","product3"]},{"type":"tag","contents":["tag1","tag2"]}]
複製程式碼
解答
乍一看要求頗多,我們一點點來拆解:
條件 1
當輸入資料不合法時,輸出空陣列
[]
什麼資料不合法?輸入值不為 JSON
格式(即 array
型別);
還有呢?輸入值為 JSON
格式(即 array
型別),但長度為 0
;
由此寫下第一句:
function groupList(list) {
if (!Array.isArray(list) || list.length === 0) { return []; }
}
複製程式碼
條件 2
當輸入資料有效時(請先過濾陣列裡的異常元素)
過濾掉[]
,空物件 null
,undefined
,數字,字串等異常元素:
function groupList(list) {
if (!Array.isArray(list) || list.length === 0) { return []; }
var validItems = getValidItems(list);
}
function getValidItems(json) {
return json.filter(function (element) {
return isPureObject(element)
});
}
function isPureObject(item) {
return Object.prototype.toString.call(item).slice(8, -1) === 'Object';
}
複製程式碼
條件 3(隱藏條件)
且慢,結構不為 { "type": "xx", "content": "yy" }
的值,是不是也為異常元素呢?為此在 getValidItems
里加上一句:
function getValidItems(json) {
return json.filter(function (element) {
return isPureObject(element) && element.type && element.content;
});
}
複製程式碼
可能有同學會問,這裡為什麼不用 typeof
判斷 object
,而是那麼麻煩擼了一個 isPureObject
呢?
首先明確 typeof
用來判斷基本型別和 function
(check: 根據不同場合使用合適的型別判斷方式),舉個例子:
var demo = [1, 2];
demo.type = '我其實是陣列';
demo.content = '但我也有 type 和 content 屬性,判斷不出來了吧';
複製程式碼
如果是
function getValidItems(json) {
return json.filter(function (element) {
return typeof element === 'object' && element.type && element.content;
});
}
複製程式碼
顯然無法過濾 demo
這種情況了,更何況還可以是 regex 等各種非 function
的物件。
所以線上程式設計題請慎重考慮邊緣情況。
條件 4
過濾完成後,將相同
type
值的元素合併,形成新元素
function groupList(list) {
if (!Array.isArray(list) || list.length === 0) { return []; }
var validItems = getValidItems(list);
var result = {};
validItems.forEach(function (item) {
if (result.hasOwnProperty(item.type)) {
result[item.type].push(item.content);
} else {
result[item.type] = [];
result[item.type].push(item.content);
}
});
return result;
}
複製程式碼
條件 5
貌似我們已經完成了將相同 type
值合併這一步驟,但是:
當前的結構是 {type1: contentsArr1, type2: contentsArr2}
的結構,與題目要求的:[{type1: contentsArr1}, {type1: contentsArr2}]
不相同。
不難,再加一步便是:
function adjustFormat(obj) {
var result = [];
Object.keys(obj).forEach(function (type) {
result.push({ type: type, contents: obj[type] });
});
return result;
}
複製程式碼
且慢,根據一個陣列產生一個新的陣列,用 array.map
是不是更物盡其用呢?(再次感謝評論區 @while大水逼 提出的方案)
更純粹的函數語言程式設計,更直觀的邏輯展示:
function adjustFormat(obj) {
return Object.keys(obj).map(function (type) {
return { type: type, contents: obj[type] };
});
}
複製程式碼
最終解答
完整的程式碼:
function groupList(list) {
if (!Array.isArray(list) || list.length === 0) { return []; }
var validItems = getValidItems(list);
var result = {};
validItems.forEach(function (item) {
if (result.hasOwnProperty(item.type)) {
result[item.type].push(item.content);
} else {
result[item.type] = [];
result[item.type].push(item.content);
}
});
return adjustFormat(result);
}
function getValidItems(json) {
return json.filter(function (element) {
return isPureObject(element) && element.type && element.content;
});
}
function isPureObject(item) {
return Object.prototype.toString.call(item).slice(8, -1) === 'Object';
}
function adjustFormat(obj) {
return Object.keys(obj).map(function (type) {
return { type: type, contents: obj[type] };
});
}
// test
var input = [null, 2, "test", undefined, {
"type": "product",
"content": "product1"
}, {
"type": "product",
"content": "product2"
}, {
"type": "tag",
"content": "tag1"
}, {
"type": "product",
"content": "product3"
}, {
"type": "tag",
"content": "tag2"
}];
console.log(JSON.stringify(groupList(input)));
// [{"type":"product","contents":["product1","product2","product3"]},{"type":"tag","contents":["tag1","tag2"]}]
複製程式碼
總結
回到文章題目本身上來,什麼算是有趣的前端筆試題?
- 不是固定套路的,不考“背誦能力”的
- 能體現 JS 能力的,考察點廣泛全面的
- 和前端密切相關的,和實際開發有聯絡的
- 注重細節複雜度,而不是純粹的“演算法複雜度”(僅針對前端開發,僅為博主一家之言)
如果是現場面試手寫程式設計題,我覺得應該再加上一條:
- 能體現程式設計素養的,注重縱向擴充的
如果哪家公司有這樣的程式設計題,請把我一波流內推帶走 :)
更多資訊,check: rayjune.me/about
有同學問為神馬沒有第三題的題解,因為自己在第二題的細節複雜度中消耗了大量時間(盯了一二十分鐘才發現需要進行 Array.isArray(input)
的判斷) - =(相信有不少同學也是這樣),沒有時間把第三題擼出來了。。。