实现 asyncSum

2020-03-29 11:04:37 +08:00
 custw

字节跳动面试题目:利用已知函数 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. 实现任意参数的求和函数 sum
  2. 求和函数依赖了异步的 add 函数,add 函数一次只能计算两数之和,所以任意参数要分组,每组两个参数调用 add 函数
  3. 重复第 2 步,直到 可分组参数数量为 1
[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]
}
1697 次点击
所在节点    问与答
11 条回复
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;
}
```
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));
}

```
chuxiaonan
2020-03-29 11:53:31 +08:00
@yuenc 哈哈 咱俩想到一块儿去了兄弟
EPr2hh6LADQWqRVH
2020-03-29 11:54:43 +08:00
你得问面试的人想要什么,想要性能,想要可读性,还是想要你解释心路历程。

有可能你想要性能,但对面想要可读性,结果驴唇不对马嘴。

你会写代码还不够,必须对面还会面试,有时候遇到对面不太会面试的,你得帮他一把。
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));
}
```
EPr2hh6LADQWqRVH
2020-03-29 12:01:30 +08:00
你看你这个题目,本质上是个 reduce 操作,要运行的过程是个计算密集型任务,那你在单线程的 js 世界里面二分并发就意义不大,平白损失了可读性,多此一举。

但如果说你是一个 io 密集型,那么你的优化就有意义。

即便优化有意义,是不是要牺牲可读性做优化也还是要看场景。
即便是进行优化,也可以最小化可读性的牺牲,把二分代码单独封装,保持业务代码简单。
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));
}

```
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;
}

```
maomaomao001
2020-03-29 13:10:31 +08:00
@avastms 这个题目为什么会用到 promise,这个我没看懂 ? 还是说, 那个 callback(a+b) 的具体实现其实是个异步?
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;
}
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
})()

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/657225

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX