认真编写的题目能测出代码水平,而有趣的题目则能激发人的“代码欲望”。
前言
前后参加过网易、美团、头条、滴滴等公司的在线编程题。简单的认为题目大体有以下三种类型:
- 认真编写的题目会促进答题者去认真的回答
- 有趣的题目会激起答题者的兴趣,以至于能激发一些『灵感』
- 没有意义的题目不值得答,这是一个双向选择
笔试题在很多时候确实是个“坑”,能避开就避开,因为毕竟像状况 2 的是少数。但优质的题目确实是一个很好的编程能力检验,遇到了就不要错过。
2018 年春招的携程前端笔试题是一个不错的例子,下面对其中的编程题做一番分析。
P.S. 原文显示效果更好喔:) check:
作者:(转载请署名,请尊重博主含辛茹苦、遍查资料、一行一行含泪码出来的成果)
常见问题
简单列举一下可能被问到的问题:
- 博主你的代码确定 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] }; });}// testvar 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:
有同学问为神马没有第三题的题解,因为自己在第二题的细节复杂度中消耗了大量时间(盯了一二十分钟才发现需要进行 Array.isArray(input)
的判断) - =(相信有不少同学也是这样),没有时间把第三题撸出来了。。。