字节跳动面试题目:利用已知函数 add
实现 asyncSum
。
function add (a, b, callback) {
callback(a + b)
}
async function asyncSum(...args) {
// 具体实现
}
// await asyncSum(1, 2, 3, 4, 5) 的结果应当为 1 + 2 + 3 + 4+ 5 = 15
解题思路:
[1, 2, 3, 4, 5] // 传参
[[1, 2], [3, 4], [5, 0]] // 分组
[3, 7, 5] // 各组求和
[[3, 7], [5, 0]] // 继续分组
[10, 5] // 继续各组求和
[[10, 5]] // 继续分组
[15] // 求和
首先,实现分组函数,将参数分成 2 个一组。
function chunk(arr) {
let ret = []
for (let i = 0; i < arr.length; i += 2) {
ret.push([arr[i], arr[i + 1] ? arr[i + 1] : 0])
}
return ret
}
chunk([1, 2, 3, 4, 5]) // [[1, 2], [3, 4], [5, 0]]
然后,实现 sum 函数对分组的数字依次求和。由于需要强依赖 add
函数,首先将 add
函数 promisify
。
function add (a, b, callback) {
callback(a + b)
}
function asyncAdd(a, b) {
return new Promise(resolve => add(a, b, sum => resolve(sum)))
}
实现 sum
函数。
function sum(nums) {
return Promise.all(chunk(nums).map(([a, b]) => asyncAdd(a, b)))
}
// await sum([1, 2, 3, 4, 5]) 结果 [3, 7, 5]
至此,可以看出数字长度在缩减,所以只需要继续拆分数组调用 sum
函数直到数字长度为 1 即为最终求和。
async function asyncSum(...args) {
let ret = await sum(args)
while (ret.length > 1) {
ret = await sum(ret)
}
return ret[0]
}
完整实现。
function chunk(arr) {
let ret = []
for (let i = 0; i < arr.length; i += 2) {
ret.push([arr[i], arr[i + 1] ? arr[i + 1] : 0])
}
return ret
}
function add (a, b, callback) {
callback(a + b)
}
function asyncAdd(a, b) {
return new Promise(resolve => add(a, b, sum => resolve(sum)))
}
function sum(nums) {
return Promise.all(chunk(nums).map(([a, b]) => asyncAdd(a, b)))
}
async function asyncSum(...args) {
let ret = await sum(args)
while (ret.length > 1) {
ret = await sum(ret)
}
return ret[0]
}
1
yuenc 2020-03-29 11:47:15 +08:00
```js
function add(a, b, callback) { callback(a + b); } async function asyncSum(...args) { // 具体实现 if (args.length === 0) { return null; } if (args.length % 2 === 1) { args.push(0); } const step = args.length / 2; const endIndex = args.length - 1; let sum = 0; for (let i = 0; i < step; i++) { sum += await new Promise(resolve => add(args[i], args[endIndex - i], resolve)); } return sum; } ``` |
2
chuxiaonan 2020-03-29 11:52:38 +08:00
楼主的解题思路非常棒 过程清晰 结果明了
不过 由于这道题题设就已经给定了 Promise + callback 的组合 而我作为面试官的话 我会把题目调整一下 会要求不允许使用 callback (当然我们知道实际业务中结合使用是完全没有问题 这里只是在面试环境下的一种理想情况下的提问而已) 那么 问题的答案就会变为 ```js // 注意这里的 add 函数不存在 callback const add = (...args) => args.reduce((sum, cur) => sum += cur, 0); const chunk = (arr) => { const ret = []; for (let i = 0; i < arr.length; i += 2) { ret.push([arr[i], arr[i + 1] ? arr[i + 1] : 0]); } return ret; }; // 其实你会发现这个函数可以不使用 async 关键字修饰 async function asyncSum(...args) { // 首先构造 promise 链 const chain = chunk(args).map((item) => (sum) => add(sum, add(...item))); // 其次构造初始化 promise 实例 let result = Promise.resolve(0); // 最后根据 promise chaining 的特性得到最终的运算结果 while (chain.length) { result = result.then(chain.shift()); } // 输出 result.then(console.log.bind(console)); } ``` |
3
chuxiaonan 2020-03-29 11:53:31 +08:00
@yuenc 哈哈 咱俩想到一块儿去了兄弟
|
4
EPr2hh6LADQWqRVH 2020-03-29 11:54:43 +08:00
你得问面试的人想要什么,想要性能,想要可读性,还是想要你解释心路历程。
有可能你想要性能,但对面想要可读性,结果驴唇不对马嘴。 你会写代码还不够,必须对面还会面试,有时候遇到对面不太会面试的,你得帮他一把。 |
5
lizheming 2020-03-29 11:57:10 +08:00
直接用 reduce 链式一波流就可以了吧,两两分组感觉很麻烦的样子…
```js async function asyncSum(...args) { return args.reduce((o, n) => new Promise(resolve => o.then(v => add(v, n, resolve)) ), Promise.resolve(0)); } ``` |
6
EPr2hh6LADQWqRVH 2020-03-29 12:01:30 +08:00 1
你看你这个题目,本质上是个 reduce 操作,要运行的过程是个计算密集型任务,那你在单线程的 js 世界里面二分并发就意义不大,平白损失了可读性,多此一举。
但如果说你是一个 io 密集型,那么你的优化就有意义。 即便优化有意义,是不是要牺牲可读性做优化也还是要看场景。 即便是进行优化,也可以最小化可读性的牺牲,把二分代码单独封装,保持业务代码简单。 |
7
yuenc 2020-03-29 12:19:34 +08:00
```javascript
题目意思应该是所有的加只能用 函数 add function add(a, b, callback) { callback(a + b); } async function asyncSum(...args) { // 具体实现 switch (args.length) { case 0: return 0; case 1: return args[0]; case 2: return new Promise(resolve => add(args[0], args[1], resolve)); } if (args.length % 2 === 1) { args.push(0); } const endIndex = args.length - 1; const promiseValues = Array.from( { length: args.length / 2 }, (_, i) => new Promise(resolve => add(args[i], args[endIndex - 1], resolve)) ); return asyncSum(...await Promise.all(promiseValues)); } ``` |
8
maomaomao001 2020-03-29 13:08:55 +08:00
有点没看懂,这题目异步想体现在哪里??
``` function add(a, b, callback) { callback(a + b); } async function asyncSum(...args) { const sum = args.flat(Number.POSITIVE_INFINITY).reduce((s, v) => { add(s, v, r => { s = r; }); return s; }, 0); return sum; } ``` |
9
maomaomao001 2020-03-29 13:10:31 +08:00
@avastms 这个题目为什么会用到 promise,这个我没看懂 ? 还是说, 那个 callback(a+b) 的具体实现其实是个异步?
|
10
rabbbit 2020-03-29 14:50:20 +08:00
没看懂,谁能给我讲讲这题想考啥?
我这么实现能算对吗? async function asyncSum(...args) { ...let num = 0; ...const callback = sum => (num = sum); ...args.forEach(i => add(num, i, callback)); ...return num; } |
11
rabbbit 2020-03-29 14:54:52 +08:00
function add (a, b, callback) {
...callback(a + b) } async function asyncSum(...args) { ...let num = 0; ...const callback = sum => (num = sum); ...args.forEach(i => add(num, i, callback)); ...return num; } (async () => { ...console.log(await asyncSum()) // 0 ...console.log(await asyncSum(1,2, 3, 4, 5)) // 15 })() |