对一道远程加法面试题的思考

88 阅读2分钟

近几天面试被问了一道服务器求和的问题,当时是循环解决的方式来完成的,虽然答上来了,但感觉可能并不是最优解,因为近两天HR并没有联系🐶;

那么我们可以看一下,是不是有更好的方式来解决,然后我们再看一下相关的同类问题,学习中归纳和分类可以帮助我们更好的融汇贯通;

原题:

const addRemote = (left, right) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(left + right);
    }, 1000);
  });
};
const add = (...inputs) => {} // 需要完成的部分

我们可以使用循环来解决,如下;

const add = async (...inputs) => {
  if (inputs.length === 1) {
    return inputs[0];
  }
  let result = inputs[0];
  for (let i = 1; i < inputs.length; i++) {
    result = await addRemote(result, inputs[i]);
  }
  return result;
};

但是这样可以发现,输出结果很慢有,因为这么写这些异步操作是串行的;所以我们下面来实现另一种更加灵活的方式:

我们可以把问题分解成两两归一的问题;例如将输入参数分成两个一组,一轮循环可以使参数长度减半;最终它们会将长度收敛成1,也就是最终结果;这种我们可以并发请求;

可能还有一个问题,就是可能是奇数个个数的数组,我们可能需要特殊处理,但是我们可以使用一个补位将问题转换为标准问题(学过数学的很多解题方式是这样的,把陌生问题转换靠到自己已经解决的问题);

我们先看一下版本一:

const add = async (...inputs) => {
  let combineInputs = [];
  if (inputs.length === 1) {
    return inputs[0];
  }
  const isOdd = inputs.length % 2;
  if (isOdd) {
    inputs.push(0);
  }
  for (let i = 0; i + 1 < inputs.length; i += 2) {
    combineInputs.push([inputs[i], inputs[i + 1]]);
  }
  return Promise.all(combineInputs.map((item) => addRemote(...item))).then(
    (adds) => {
      return add(...adds);
    }
  );
};

这种是我们通过Promise的递归来解决的,我们还可以用await+循环方式来解决;

const add = async (...inputs) => {
  if (inputs.length === 1) {
    return inputs[0];
  }

  let results = inputs;
  do {
    let combineInputs = [];
    const isOdd = results.length % 2;

    if (isOdd) {
      results.push(0);
    }

    for (let i = 0; i + 1 < results.length; i += 2) {
      combineInputs.push([results[i], results[i + 1]]);
    }
    results = await Promise.all(
      combineInputs.map((item) => addRemote(...item))
    );
  } while (results.length > 1);
  return results[0];
};

那么我们在冰河时代下这么答就稳了么,其实我们可以再拓展下,比如如果做了20个数的加法,后端要求不要把10多个请求一下子发过去,是不是可以控制一下;

所以我们可以包裹下Promise.all, 用controlParallel 来控制下;

const controlParallel = async (inputs, count) => {
  let result = [];
  for (let i = 0; i < inputs.length; i += count) {
    let subResult = await Promise.all(
      inputs.slice(i, i + count).map((item) => addRemote(...item))
    );
    result = result.concat(subResult);
  }
  return result;
};

这样我们就可以通过count参数来控制并发请求的数量了,调成1, 相当于我们开头的解法(只能说相当于),进一步如果想并发的请求数量可以在接口交互时协商取得count, 动态修正循环的步进长度;

最终版代码如下(菜狗只能搞到这了),大家有不同意见可以放评论区哈,不一定是最好的答案哈(仅做参考);

const controlParallel = async (inputs, count) => {
  let result = [];
  for (let i = 0; i < inputs.length; i += count) {
    let subResult = await Promise.all(
      inputs.slice(i, i + count).map((item) => addRemote(...item))
    );
    result = result.concat(subResult);
  }
  return result;
};
const getAdd = parallelCount => async (...inputs) => {
   if (inputs.length === 1) {
    return inputs[0];
  }

  let results = inputs;
  do {
    let combineInputs = [];
    const isOdd = results.length % 2;

    if (isOdd) {
      results.push(0);
    }

    for (let i = 0; i + 1 < results.length; i += 2) {
      combineInputs.push([results[i], results[i + 1]]);
    }
    results = await controlParallel(combineInputs, parallelCount);
  } while (results.length > 1);
  return results[0];
};
const add = getAdd(3);

add(1).then((result) => {
  console.log({ result });
});

add(1, 8, 9, 21, 20, 0).then((result) => {
  console.log({ result });
});