我建议这样优化双重 for 循环

2,163 阅读4分钟

写在前面

作为一名程序员,相信大家在开发当中对于 for 循环的使用是非常非常频繁的,不知道大家有没有写过或者见别人写过类似下面的代码

for (let index = 0; index < array1.length; index++) {
  for (let index2 = 0; index2 < array2.length; index2++) {
    //.....
  }
}

这样的代码,应该挺常见的,这就是大名鼎鼎的双重 for 循环!!

不知道大家看到上面的代码什么感觉,先不说其他的,大家在工作当中会不会写类似的代码呢?

在工作当中,我们为了完成需求,一开始可能不会考虑代码质量或者优化相关的,但是如果需求完成之后是不是可以回过头来优化一下自己写的代码,这样也以防以后别人维护你的代码时候不会.....

相信大家都有自己的想法,在一些简单场景下,还是尽量不要使用双重for循环。当然,一些非常复杂的场景,非用不可的话也没事!具体情况具体分析嘛,就是不要无脑使用就可以。

为什么

时间复杂度

什么是时间复杂度?大家可以参考这篇文章

(算法入门)人人都能看懂的时间复杂度和空间复杂度 - 掘金 (juejin.cn)

对于双重 for 循环,其时间复杂度通常表示为 O(n^2),其中 n 是输入规模。

在双重 for 循环中,假设外层一共循环 n 次,外层循环每执行 1 次,内层循环都会执行 n 次。因此,总共的执行次数为 n * n = n^2。这就意味着随着输入规模 n 的增加,执行次数呈二次方增长。

这种二次方增长会导致算法的执行时间随着输入规模的增加而迅速增加,特别是当 n 很大时。因此,双重 for 循环可能会导致性能问题,尤其是在处理大规模数据或复杂算法时。

大概就是我们在写代码的时候尽量将时间复杂度想办法降低到 O(n),也就是一个 for 循环,而不是双重 for 循环,这有助于优化你的代码执行时间。

解决

大家可以根据我的这个小小的需求,来结合实现

对于数组 arr1,我们希望在 arr2 中与 arr1 的 id 一致的那一部分,将 arr1 的 disabled 属性设置位 true,也就是进行数据筛选。

  let arr1 = [
    {
      id: 1,
      disabled: false,
    },
    {
      id: 2,
      disabled: false,
    },
    {
      id: 3,
      disabled: false,
    },
    {
      id: 4,
      disabled: false,
    },
    {
      id: 5,
      disabled: false,
    },
    {
      id: 6,
      disabled: false,
    },
  ]
  let arr2 = [
    {
      id: 1,
    },
    {
      id: 8,
    },
    {
      id: 3,
    },
    {
      id: 9,
    },
  ]

双重for循环

使用双重 for 循环很容易解决,这个没啥说的了

  arr1.forEach((item) => {
    arr2.forEach((elemment) => {
      if (elemment.id === item.id) {
        item.disabled = true
      }
    })
  })

优化双重for循环

类似于上面的需求,我们都可以用下面的方案进行解决,很好理解

使用 Map 进行优化,还不知道 Map 咋用的我就不说了,自己看文档吧...

Map - JavaScript | MDN (mozilla.org)

第一步

将内层 for 循环所需要比较的条件存到 Map 中,使用 set 方法进行存储

  const BMap = new Map()
  arr2.forEach((item) => {
    BMap.set(item.id, true)
  })

第二步

循环外层数组,并判断是否存在在 map 中,使用 has 方法进行判断,如果存在设置相应属性即可

  arr1.forEach((item) => {
    if (BMap.has(item.id)) {
      item.disabled = true
    }
  })

在使用 has 方法查找 Map 中是否存在指定键时,时间复杂度是O(1)。

这是因为 Map 内部使用了哈希表,可以在常量时间内(即O(1))找到指定的键对应的值。

这里不建议使用 find 方法进行实现,比如:

arr1.forEach((item) => {
  const found = arr2.find(element => element.id === item.id);
  if (found) {
    item.disabled = true;
  }
});

虽然这段代码使用了 find 方法,但是在每次查找时都需要遍历 arr2 数组,因此时间复杂度可能会超过 O(n)

最终代码

  const BMap = new Map()
  arr2.forEach((item) => {
    BMap.set(item.id, true)
  })

  arr1.forEach((item) => {
    if (BMap.has(item.id)) {
      item.disabled = true
    }
  })

总结

使用 Map 对象,提高了代码的简洁性、易读性,保证了时间复杂度维持在 O(n),所以笔者建议大家以后再遇到类似的需求,可以首先往 Map 这里想一下,而不要下意识的就直接双重for循环了,当然也需要具体问题具体分析,非常复杂的场景不太好这样使用的话,那用双层for循环也可以。