Another RayJune

有趣的前端编程题:2018 春招

认真编写的题目能测出代码水平,而有趣的题目则能激发人的“代码欲望”

前言

前后参加过网易、美团、头条、滴滴等公司的在线编程题。简单的认为题目大体有以下三种类型:

  1. 认真编写的题目会促进答题者去认真的回答
  2. 有趣的题目会激起答题者的兴趣,以至于能激发一些『灵感』
  3. 没有意义的题目不值得答,这是一个双向选择

笔试题在很多时候确实是个“坑”,能避开就避开,因为毕竟像状况 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 的,别问我为什么知道 :>

  • 为什么博主你的代码没有杂糅在一起,而是合理的抽出为多个函数呢?

一种编程品味

编程风格

简单陈述一下文中代码使用的编程风格:

  1. 使用 ES5,以避免有些在线编程平台不支持 ES6 的状况(所以在这里没有用 new Set()
  2. Airbnb 代码规范,不使用单 var 模式
  3. 变量定义(var),函数体(body),return 值三者用空行隔开,逻辑鲜明
  4. 有意义的变量命名
  5. 适当的函数抽取,一个函数只做一件事情

另外还有 根据不同场合使用合适的类型判断方式

  • Array.isArray 判断数组,
  • Object.prototype.toString.call 来判断纯对象
  • typeof 判断基本类型和 function
  • instanceof 来判断自定义的对象

一. 字符串截取

题目

描述

给定一个长度小于 50 且包含字母和数字的任意字符串,要求按顺序取出当中的数字和英文字母,数字需要去重,重新排列后的字符串数字在前,字母在后。

输入

需要截取的字符串(包含数字和字母)

输出

按照要求重新排列的字符串

样例输入

1
'携程C2t0r1i8p2020校招'

样例输出:

1
'2018Ctrip'

解答

肯定有同学表示第一题不值得分析。但我还是想抛砖引玉一下,思路如下:

  1. 字符串转数组str.split('')进而使用数组的各种操作方法,如 arr.forEach
  2. 判断字符串值是否为数字/\d/.test(element) 或者 Number.isNaN(Number(element)
  3. 判断字符串值是否为字母/[a-zA-Z]/.test(element)
  4. 数字字符串去重(利用数组去重)
  5. 输出数字+字母

由此有了这一版代码:

条件 1~3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

去重

作为前端开发超高频面试题,相信大家早已对数组去重熟捻于心:

基本类型去重:

1
2
3
4
5
function unique(arr) {
return arr.filter(function (element, index) {
return arr.indexOf(element) === index;
});
}

基本+复杂类型去重:

1
2
3
4
5
6
7
8
9
10
11
12
function unique(arr) {
var hash = {};

return arr.filter(function (element) {
if (hash.hasOwnProperty(element)) {
return false;
}
hash[element] = true;

return true;
});
}

由于数字去重(str,基本类型)基于数组去重,我们要对原来的数组去重做一点修改:

1
2
3
4
5
6
7
function uniqueStr(str) {
var arr = str.split('');

return arr.filter(function (element, index) {
return arr.indexOf(element) === index;
}).join('');
}

string.split()array.join() 帮助我们自由的游走在字符串和数组间。

最终解答 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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) 的方法。实现更简洁、逻辑的展示更好,在此补上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 类型分组成二维数组

输入

  • 输入的参数可能是空数组 [],空对象 nullundefined,数字,字符串等异常值;
  • 也可能是结构为 [{ type, content}] 的有效值;
  • 甚至是 [null, null, (type, content)] 等有效和非法值混合的数据。

输出

  • 当输入数据不合法时,输出空数组 []
  • 当输入数据有效时(请先过滤数组里的异常元素),然后将相同 type 值的元素合并,形成新元素 {"type": "A", "contents": [content1, content2]},其中,contents 为一个数组,元素为所有 type 值相同的 content 值。
  • 注意,输出的是一个标准 JSON 格式

样例输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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"
}];

样例输出

1
[{"type":"product","contents":["product1","product2","product3"]},{"type":"tag","contents":["tag1","tag2"]}]

解答

乍一看要求颇多,我们一点点来拆解:

条件 1

当输入数据不合法时,输出空数组 []

什么数据不合法?输入值不为 JSON 格式(即 array 类型)

还有呢?输入值为 JSON 格式(即 array 类型),但长度为 0;

由此写下第一句:

1
2
3
function groupList(list) {
if (!Array.isArray(list) || list.length === 0) { return []; }
}

条件 2

当输入数据有效时(请先过滤数组里的异常元素)

过滤掉[],空对象 nullundefined,数字,字符串等异常元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 里加上一句:

1
2
3
4
5
function getValidItems(json) {
return json.filter(function (element) {
return isPureObject(element) && element.type && element.content;
});
}

可能有同学会问,这里为什么不用 typeof 判断 object,而是那么麻烦撸了一个 isPureObject 呢?

首先明确 typeof 用来判断基本类型和 function(check: 根据不同场合使用合适的类型判断方式),举个例子:

1
2
3
4
var demo = [1, 2];

demo.type = '我其实是数组';
demo.content = '但我也有 type 和 content 属性,判断不出来了吧';

如果是

1
2
3
4
5
function getValidItems(json) {
return json.filter(function (element) {
return typeof element === 'object' && element.type && element.content;
});
}

显然无法过滤 demo 这种情况了,更何况还可以是 regex 等各种非 function 的对象。

所以在线编程题请慎重考虑边缘情况。

条件 4

过滤完成后,将相同 type 值的元素合并,形成新元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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}] 不相同。

不难,再加一步便是:

1
2
3
4
5
6
7
8
9
function adjustFormat(obj) {
var result = [];

Object.keys(obj).forEach(function (type) {
result.push({ type: type, contents: obj[type] });
});

return result;
}

且慢,根据一个数组产生一个新的数组,用 array.map 是不是更物尽其用呢?(再次感谢评论区 @while大水逼 提出的方案)

更纯粹的函数式编程,更直观的逻辑展示:

1
2
3
4
5
function adjustFormat(obj) {
return Object.keys(obj).map(function (type) {
return { type: type, contents: obj[type] };
});
}

最终解答

完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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) 的判断) - =(相信有不少同学也是这样),没有时间把第三题撸出来了。。。

文章标题:有趣的前端编程题:2018 春招

文章作者:RayJune

时间地点:晚 11:30,于桂苑自习教室

原始链接:https://www.rayjune.me/2018/03/31/Interesting-programming-problems-Ctrip-2018-Spring-recruit/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。