首页 > 解决方案 > 使用 reduce() 同时读取两个变量

问题描述

我想用 来降低以下函数的复杂性reduce(),因为这两个变量的功能几乎相似,selectedEnrolledselectedNotEnrolled.

我尝试使用map(),但在这里我没有返回任何东西,所以最终产生了副作用,我意识到这是一种糟糕的编码习惯。

countSelectNotEnrolled(selected: any) {
  this.selectedNotEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 1 : 0)
      : count;
  }, 0);
  this.selectedEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 0 : 1)
      : count;
  }, 0);
}

我很感激帮助。

标签: javascriptangulartypescriptrxjs

解决方案


唯一的区别似乎是您作为增量返回的内容,具体取决于当前 id 是否是this.userShelf.courseIds数组的一部分。

countSelectNotEnrolled(selected: any) {
  this.selectedNotEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 1 : 0)
      //                                                       ^^^^^
      : count;
  }, 0);
  this.selectedEnrolled = selected.reduce((count, id) => {
    return this.hasSteps(id)
      ? count + (this.userShelf.courseIds.indexOf(id) === -1 ? 0 : 1)
      //                                                       ^^^^^
      : count;
  }, 0);
}

一种更普遍适用于重构模式的方法是在创建函数时将该差异提取为动态部分。

您可以创建一个返回另一个函数并接受此增量值作为参数的函数。然后,您可以使用该函数创建两个函数,一个对计数selectedNotEnrolled求和,另一个对 求和selectedEnrolled

注意: usingArray.prototype.includesArray.prototype.indexOf检查数组中的存在性要干净一些。

function createEnrolmentSum( // the wrapper function accepts the dynamic setup logic
  incrementIfSelected,
  incrementIfUnselected = incrementIfSelected === 1 ? 0 : 1
) {
  return function (selected) { // <--- return a function that does the common logic
    return selected.reduce((count, id) => {
      return this.hasSteps(id)
        ? count +
            (this.userShelf.courseIds.includes(id) // <-- using includes is a bit cleaner
              ? incrementIfSelected
              : incrementIfUnselected)
        : count;
    }, 0);
  };
}

// ...

// create instance methods so we maintain proper `this` behavior
getEnrolledSum = createEnrolmentSum(1); 
getNotEnrolledSum = createEnrolmentSum(0);

countSelectNotEnrolled(selected: any) {
  this.selectedNotEnrolled = this.getNotEnrolledSum();
  this.selectedEnrolled = this.getEnrolledSum();
}

以上只是对如何重构任何类似代码的通用演示。可以说,这个特定的 api 不是很可读:

// this api isn't very readable because it's not clear
// what `1` or `0` mean as arguments
getEnrolledSum = createEnrolmentSum(1);
getNotEnrolledSum = createEnrolmentSum(0);

您可以使用配置对象提高这种可读性:

getEnrolledSum = createEnrolmentSum({
  incrementIfSelected: 1,
  incrementIfUnselected: 0
});
getNotEnrolledSum = createEnrolmentSum({
  incrementIfSelected: 0,
  incrementIfUnselected: 1
});

但这并没有带来太大的改善,因为代码虽然DRY,但绝对是复杂的。

我建议,对于您的特定情况,显而易见的起始解决方案是在一个循环中计算两个总和。这不需要太多复杂性并且会更快,因为您只需遍历selected数组一次而不是两次。

countSelectNotEnrolled(selected) {
  let selectedNotEnrolled = 0,
      selectedEnrolled = 0;

  for (const id of selected) {
    if (this.hasSteps(id)) {
      if (this.userShelf.courseIds.includes(id)) {
        selectedEnrolled += 1;
      } else {
        selectedNotEnrolled += 1;
      }
    }
  }

  this.selectedNotEnrolled = selectedNotEnrolled;
  this.selectedEnrolled = selectedEnrolled;
}

如果您确实想使用数组缩减,您可以使用一个对象通过迭代循环携带两个变量:

countSelectNotEnrolled(selected) {
  const { selectedNotEnrolled, selectedEnrolled } = selected.reduce(
    (result, id) => {
      if (this.hasSteps(id)) {
        if (this.userShelf.courseIds.includes(id)) {
          result.selectedEnrolled += 1;
        } else {
          result.selectedNotEnrolled += 1;
        }
      }
      return result;
    },
    { selectedNotEnrolled: 0, selectedEnrolled: 0 } // <-- reduction result contains both variables
  );

  this.selectedNotEnrolled = selectedNotEnrolled;
  this.selectedEnrolled = selectedEnrolled;
}

去除一些嵌套,我们可以先过滤掉所有没有step的id:

countSelectNotEnrolled(selected) {
  const { selectedNotEnrolled, selectedEnrolled } = selected
    .filter(this.hasSteps) // <-- apply the filter first
    .reduce(
      (result, id) => {
        if (this.userShelf.courseIds.includes(id)) {
          result.selectedEnrolled += 1;
        } else {
          result.selectedNotEnrolled += 1;
        }
        return result;
      },
      { selectedNotEnrolled: 0, selectedEnrolled: 0 }
    );

  this.selectedNotEnrolled = selectedNotEnrolled;
  this.selectedEnrolled = selectedEnrolled;
}

如果您已经在实例上初始化了必要的变量并且更新实例数据没有重大的性能损失,您也可以直接为其赋值:

countSelectNotEnrolled(selected) {
  // setup
  this.selectedEnrolled = 0;
  this.selectedNotEnrolled = 0;

  // sum
  selected
    .filter(this.hasSteps)
    .forEach(id => {
      if (this.userShelf.courseIds.includes(id)) {
        this.selectedEnrolled += 1;
      } else {
        this.selectedNotEnrolled += 1;
      }
    });
}

或者,您可以利用过滤来拆分属于已注册和未注册的 id,然后提取长度:

countSelectNotEnrolled(selected) {
  const selectedWithSteps = selected.filter(this.hasSteps);

  this.selectedNotEnrolled = selectedWithSteps.filter(
    id => !this.userShelf.courseIds.includes(id)
  ).length;

  this.selectedEnrolled = selectedWithSteps.filter(id =>
    this.userShelf.courseIds.includes(id)
  ).length;
}

其实不用过滤两次,就可以互相表示已注册和未注册:

countSelectNotEnrolled(selected) {
  const selectedWithSteps = selected.filter(this.hasSteps);

  this.selectedNotEnrolled = selectedWithSteps.filter(
    id => !this.userShelf.courseIds.includes(id)
  ).length;

  this.selectedEnrolled = selectedWithSteps.length - this.selectedNotEnrolled;
}

总的来说,我给出的最好的重构建议是减少对特定模式的担忧,而更多地关注代码的可读性。我们正在使用 JavaScript 等高级语言编写,以便人类可以阅读它,机器不在乎,它们可以很好地阅读二进制文件。我们应该编写花费最少的人类认知努力来理解的代码


推荐阅读