认真编写的题目能测出代码水平,而有趣的题目则能激发人的“代码欲望”。
前言
前后参加过网易、美团、头条、滴滴等公司的在线编程题。简单的认为题目大体有以下三种类型:
- 认真编写的题目会促进答题者去认真的回答
- 有趣的题目会激起答题者的兴趣,以至于能激发一些『灵感』
- 没有意义的题目不值得答,这是一个双向选择
笔试题在很多时候确实是个“坑”,能避开就避开,因为毕竟像状况 2 的是少数。但优质的题目确实是一个很好的编程能力检验,遇到了就不要错过。
2018 年春招的携程前端笔试题是一个不错的例子,下面对其中的编程题做一番分析。
P.S. 原文显示效果更好喔:) check:https://www.rayjune.me/2018/03/31/interesting-programming-problems-ctrip-2018-spring-recruit/
作者: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 能力的,考察点广泛全面的
- 和前端密切相关的,和实际开发有联系的
- 注重细节复杂度,而不是纯粹的“算法复杂度”(仅针对前端开发,仅为博主一家之言)
如果是现场面试手写编程题,我觉得应该再加上一条:
- 能体现编程素养的,注重纵向拓展的
如果哪家公司有这样的编程题,请把我一波流内推带走 :)
号外:博主为 18 届应届生,目前状态是前端开发补招进行时。如有内推机会,欢迎一波带走 :》 欢迎 check 在线简历:resume.pdf
有同学问为神马没有第三题的题解,因为自己在第二题的细节复杂度中消耗了大量时间(盯了一二十分钟才发现需要进行 Array.isArray(input)
的判断) - =(相信有不少同学也是这样),没有时间把第三题撸出来了。。。