【问题标题】:How to avoid for loop inside for loop in javascript如何避免javascript中for循环内部的for循环
【发布时间】:2018-01-23 22:04:00
【问题描述】:

我编写了一段运行良好的代码。我想要一个由 myArr 中的元素按 orderArr 中指定的顺序组成的新数组。但是,它在另一个 for 循环中使用 for 循环来匹配数组元素。

var myArr = ['a', 'b', 'c', 'd', 'e'];
var orderArr = ['e', 'c'];
var reArr = [];

for (var i = 0; i < orderArr.length; i++) {
  for (var k = 0; k < myArr.length; k++) {
    if (myArr[k] == orderArr[i]) {
      reArr.push(myArr[k]);
    }
  }
}

console.log(reArr);

我经常听说在另一个 for 循环中使用 for 循环是不好的做法,甚至应该避免使用 forEach。

我还能如何重写这段代码。

【问题讨论】:

  • “我经常听说在另一个 for 循环中使用 for 循环是不好的做法” 特别是从任何地方?虽然编写此代码的方法可能更短,但在内部,他们可能仍会使用几个循环来完成工作。
  • :-) 来自项目中的其他开发人员。他们说这是性能问题。我不确定如何...
  • 询问他们打算如何可靠和准确地测量它以表明这是一个性能问题......!我敢打赌,最后对console.log 的单个调用比所有循环组合起来要慢得多...
  • 你的最终目标是什么?嵌套的 for 循环是正确工作的正确工具。在这种情况下,您可能需要 Array#includes 来代替

标签: javascript for-loop foreach


【解决方案1】:

我不一定会说在循环中使用循环是一种不好的做法——事实上,Ori Drori 比我说这种做法的效率可能仅仅取决于数据集的大小。

例如,sorting algorithms 的实现有很多,您经常会在循环中找到循环。但是,随着数据大小的变化,实现的细节会影响性能。

我个人的经验是,当我看到嵌套循环时,我会立即问自己,它所执行的操作是否需要使用不同的算法进行优化。在 JavaScript(浏览器)应用程序中,答案往往是“不”,因为数据量很少大到足以产生影响。

【讨论】:

    【解决方案2】:

    在您的情况下,嵌套循环的复杂性是 O(n * m) - n 是 orderArr 的长度,m 是 myArr 的长度。

    此解决方案的复杂度为 O(n + m),因为我们使用复杂度为 O(m) 的 Array#reduce 创建字典对象,然后使用复杂度为 O(n) 的 filtering orderArray 创建字典对象.

    注意 1: 对于小型数组,这并不重要。因此,除非您在两个数组中都有数千个,否则您可以继续使用嵌套循环。

    注意2:此代码假定myArr 中没有重复项。如果有重复,结果会与嵌套循环不同。

    var myArr = ['a', 'b', 'c', 'd', 'e'];
    var orderArr = ['e', 'c'];
    var myArrDict = myArr.reduce(function(r, s) {
      r[s] = true;
      return r;
    }, Object.create(null));
    
    var reArr = orderArr.filter(function(s) {
      return this[s];
    }, myArrDict);
    
    console.log(reArr);

    【讨论】:

    • 这种方法的一个问题是它不会在返回的数组中保留原始数组的重复项(循环代码会这样做)。
    • @SBFrancies - 这是一个很好的观点,我没有考虑过。我会添加注释。
    【解决方案3】:

    我认为您的代码没有任何问题,但如果您真的愿意,可以使用以下代码消除(对您可见的)循环:

    var reArr = myArr.filter((n) => orderArr.includes(n));
    

    (不适用于旧版本的 IE)。

    【讨论】:

    • Will not work in old versions of IE 除非您将 polyfills 用于过滤/包含方法
    【解决方案4】:

    您可以做的是合并两个数组并使用reduce 删除除重复项之外的所有内容,如下所示:

    var myArr = ['a', 'b', 'c', 'd', 'e'];
    var orderArr = ['e', 'c'];
    // Merge the two arrays
    var reArr = myArr.concat(orderArr);
    
    let result = reArr.reduce((arr, val) => {
      // return only equal values that are not already in the new array
      // If the value is in the new array 'indexOf()' will be greater than -1
      // we will then not modify the current array
      //
      // If the value is not in the new array and is equal to the current lookup 
      // then add it to the filter. Once filtered, we can check
      // if the filter contains more than one item if it does
      // then we can add it to the new array
      return reArr.filter(v => v == val && arr.indexOf(val) == -1).length > 1 
        // If it is not in the new array add it using 'concat()'
        // otherwise we will return the current array as is
        ? arr.concat(val) : arr
    }, [])
    
    console.log(result);

    【讨论】:

      【解决方案5】:

      为了补充这里的一些好的答案(尤其是 Ori 的),我相信两个非常大的数组的最佳解决方案是合并连接(这是关系数据库处理这种情况的方式。这里的想法是我们只麻烦比较一下我们知道的值大致相同。

      第一步是对两个数组进行排序 (O(n log n) + O(m log m)),然后比较每个数组中的第一个值。有3种可能:

      A<B
        We discard A and fetch the next value from the same list A came from
      A=B
        We add A to the keep list and retrieve the next value from that array
      A>B
        We discard B and retrieve the next value from its list
      

      ...并重复该过程,直到其中一个列表耗尽...

       O(n log n) + O(m log m) + O(least(n,m))
      

      【讨论】:

        【解决方案6】:

        就我个人而言,我喜欢为这个特定场景提供一个专用的 correlate 函数。

        "use strict";
        
        function correlate(
          outer,
          inner, 
          outerKeyOrKeySelector = x => x,
          innerKeyOrKeySelector = x => x
        ) {
          const outerValues = [...outer];
          const innerValues = [...inner];
          const outerToKey = typeof outerKeyOrKeySelector === 'function'
              ? outerKeyOrKeySelector
              : (x => x[outerKeyOrKeySelector]);
        
          const innerToKey = typeof innerKeyOrKeySelector === 'function'
              ? innerKeyOrKeySelector
              : (x => x[innerKeyOrKeySelector]);
        
          const outerKeyed = outerValues.map(value => ({key: outerToKey(value), value});
          const innerKeyed = innerValues.map(value => ({key: innerToKey(value), value});
        
          return outerKeyed.reduce((results, {key: oKey, value: oValue}) => [
             ...results,
             ...innerKeyed
                 .filter(({key: iKey}) => oKey === iKey)
                 .map(({value: iValue}) => [oValue, iValue])
            ], []);
        }
        

        这基本上就像 JOIN 一样,允许您根据属性值或任意投影函数关联两个数组。

        它可能看起来有点矫枉过正,YMMV,但我觉得它非常有用。

        应用于手头的问题:

        reArr = correlate(orderArr, myArr).map(([first, second]) => first);
        

        但它处理复杂的场景同样容易

        reArr = correlate(orders, customers, o => o.customer.name, c => c.name)
          .map(([first, second]) => {order: first, customer: second})
          .forEach(({customer, order}) => {
            customer.orders = [...customer.orders, order];
          });
        

        【讨论】:

          【解决方案7】:

          myArr 映射到一个对象中。我将在myArr 中添加一个“a”,在orderArr 中添加一个“f”。

          var myArr = ['a', 'a', 'b', 'c', 'd', 'e'];
          var orderArr = ['e', 'c', 'f']
          var myObj = createObjectFrom(myArr);
          var reArr = [];
          
          function createObjectFrom(array) {
             var obj = {};
             var arrValue;
          
             for (var i = 0; i < array.length; i++) {
               arrValue = array[i];
          
               if (obj[arrValue]) {
                 obj[arrValue]++;
               } else {
                 obj[arrValue] = 1;
               }
             }
          
             return obj;  // { 'a': 2, 'b': 1, 'c': 1, 'd': 1, 'e': 1 }
          }
          
          var orderValue;
          
          for(var i=0;i<orderArr.length;i++){
            orderValue = orderArr[i];
          
            if (myObj.hasOwnProperty(orderValue)) {
               for (var j = 0; j < myObj[orderValue]; j++) {
                 reArr.push(orderValue);
               }
             }
          }
          
          console.log(reArr);  // [ "c", "e" ]
          

          您只需循环 (6+3=) 9 次,而不是循环 (6*3=) 18 次。

          为了考虑重复,我使代码稍微复杂一些,因此是第二个 for 循环 (j)。


          作为额外的奖励,这里有一个我认为您可以观看的视频:

          https://www.youtube.com/watch?v=XKu_SEDAykw

          【讨论】:

            【解决方案8】:

            您可以使用Map 并将所有相同的值收集到一个数组中,以便以后与结果集连接。

            var array = ['a', 'b', 'c', 'd', 'e'],
                order = ['e', 'c'],
                map = new Map,
                result;
                
            array.forEach(v => map.set(v, (map.get(v) || []).concat(v)));
            
            result = order.reduce((r, v) => r.concat(map.get(v)), []);
            
            console.log(result);

            【讨论】:

              【解决方案9】:

              这是一个非常简单的 ES5 版本 - 内部循环避免器,前提是您的值不是对象:

              var myArr = ['a', 'b', 'c', 'd', 'e'];
              var orderArr = ['e', 'c'];
              var myArrIndex = {};
              var reArr = [];
              
              for (var i = 0; i < myArr.length; i++) {
                  myArrIndex[myArr[i]] = myArr[i];
              };
              
              for (var i = 0; i < orderArr.length; i++) {
                  var orderArrayItem = orderArr[i];
                  if (myArrIndex[orderArrayItem]) {
                      reArr.push(orderArrayItem);
                  }
              }
              
              
              console.log(reArr);

              【讨论】:

              • 你测试过你的代码吗?其中有语法错误。以及地图使用中的巨大错误。
              • @RomainVincent - 修复了代码 sn-p。你是对的。有几个问题
              猜你喜欢
              • 1970-01-01
              • 2015-03-21
              • 2022-01-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多