V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
custw
V2EX  ›  问与答

实现 asyncSum

  •  
  •   custw · 2020-03-29 11:04:37 +08:00 · 1838 次点击
    这是一个创建于 1703 天前的主题,其中的信息可能已经有所发展或是发生改变。

    字节跳动面试题目:利用已知函数 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]
    }
    
    11 条回复    2020-03-29 14:54:52 +08:00
    yuenc
        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;
    }
    ```
    chuxiaonan
        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));
    }

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

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

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

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

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

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

    ```
    maomaomao001
        9
    maomaomao001  
       2020-03-29 13:10:31 +08:00
    @avastms 这个题目为什么会用到 promise,这个我没看懂 ? 还是说, 那个 callback(a+b) 的具体实现其实是个异步?
    rabbbit
        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;
    }
    rabbbit
        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
    })()
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3139 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 13:32 · PVG 21:32 · LAX 05:32 · JFK 08:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.