使用reduce()同时读取两个变量

时间:2018-12-26 05:30:38

标签: javascript angular typescript rxjs

我想用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);
}

感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

唯一的区别似乎是您要返回的增量,具体取决于当前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的计数求和。

注意:在检查数组中的存在时,使用Array.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;
}

要删除某些嵌套,我们首先可以过滤掉没有步骤的所有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划分为已注册和未注册的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之类的高级语言进行编写,以便人类可以阅读它,而机器不在乎,他们可以很好地阅读二进制文件。我们应该编写花费最少的人类认知努力来理解的代码