【问题标题】:Javascript - insert an array inside another arrayJavascript - 在另一个数组中插入一个数组
【发布时间】:2011-08-11 20:41:19
【问题描述】:

在另一个数组中插入一个数组更有效的方法是什么。

a1 = [1,2,3,4,5];
a2 = [21,22];

newArray - a1.insertAt(2,a2) -> [1,2, 21,22, 3,4,5];

如果 a2 数组很大,从性能的角度来看,使用 splice 迭代 a2 看起来有点糟糕。

谢谢。

【问题讨论】:

标签: javascript arrays performance


【解决方案1】:

您可以使用splice 结合一些apply 诡计:

a1 = [1,2,3,4,5];
a2 = [21,22];

a1.splice.apply(a1, [2, 0].concat(a2));

console.log(a1); // [1, 2, 21, 22, 3, 4, 5];

在 ES2015+ 中,您可以使用扩展运算符来使它更好一点

a1.splice(2, 0, ...a2);

【讨论】:

  • @icCube:我刚刚运行了一个基准测试,将这种方法与 patrick 的回答进行了比较。它从示例中的 a1 和 a2 开始,并将 a2 变量注入 a1 10,000 次(这样最后,您就有了一个包含 20,005 个元素的数组)。此方法耗时:81ms,slice + concat 方法耗时156ms(在 Chrome 13 上测试)。当然,这附带一个标准警告,即在大多数情况下,易读性比速度更重要。
  • @nick:在 chrome 中,如果数组包含超过 130000 个项目,apply 将引发 stackoverflow 异常。 jsfiddle.net/DcHCY 也发生在 jsPerf 我相信 FF 和 IE 的阈值在 500k 左右
  • 为什么使用 apply 而不是直接调用 splice 并传递参数?
  • @FrançoisWahl,这是人们在回答中经常忽略的一个严重问题。我以您的示例为例,使其适用于 1M 元素以北:stackoverflow.com/a/41466395/1038326
  • 我刚刚尝试过这个(2017 年 6 月),小数组大小和大数组大小(在 Chrome、FF 和 Edge 上)的最快方法似乎是 target.slice(0, insertIndex).concat(insertArr, target.slice(insertIndex)); jsperf.com/inserting-an-array-within-an-array
【解决方案2】:

如果使用 ES2015 或更高版本,您现在可以执行此操作:

var a1 = [1,2,3,4,5];
var a2 = [21,22];
a1.splice(2, 0, ...a2);
console.log(a1) // => [1,2,21,22,3,4,5]

有关展开 (...) 运算符 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator 的文档,请参阅此文档

【讨论】:

    【解决方案3】:

    一开始是错的。应该改用concat()

    var a1 = [1,2,3,4,5],
        a2 = [21,22],
        startIndex = 0,
        insertionIndex = 2,
        result;    
    
    result = a1.slice(startIndex, insertionIndex).concat(a2).concat(a1.slice(insertionIndex));
    

    示例: http://jsfiddle.net/f3cae/1/

    此表达式使用slice(0, 2)[docs] 返回a1 的前两个元素(其中0 是起始索引,2 是元素deleteCount,尽管a1 没有改变)。

    中间结果[1,2]

    然后它使用concat(a2)[docs]a2 附加到[1,2] 的末尾。

    中间结果[1,2,21,22].

    接下来,a1.slice(2) 在此表达式末尾的尾随 .concat() 中被调用,相当于 [1,2,21,22].concat(a1.slice(2))

    调用slice(2),具有正整数参数,将返回第二个元素之后的所有元素,按自然数计数(如,有五个元素,所以[3,4,5]将从a1返回) .另一种说法是,奇异整数索引参数告诉a1.slice() 从数组中的哪个位置开始返回元素(索引 2 是第三个元素)。

    中间结果[1,2,21,22].concat([3,4,5])

    最后,第二个.concat()[3,4,5] 添加到[1,2,21,22] 的末尾。

    结果[1,2,21,22,3,4,5]

    更改Array.prototype 可能很诱人,但可以简单地使用原型继承扩展 Array 对象并将所述新对象注入到您的项目中。

    但是,对于那些生活在边缘的人......

    示例: http://jsfiddle.net/f3cae/2/

    Array.prototype.injectArray = function( idx, arr ) {
        return this.slice( 0, idx ).concat( arr ).concat( this.slice( idx ) );
    };
    
    var a1 = [1,2,3,4,5];
    var a2 = [21,22];
    
    var result = a1.injectArray( 2, a2 );
    

    【讨论】:

    • 我得到的 a1 有 6 项而不是 7 -> [1,2,[21,22],3,4,5]
    • 有效!非常感谢,让我们等几天,但这是最好的答案......奇怪的是他们没有这个的 js 功能。
    • 总之,JS引擎必须在表达式完成之前返回四个数组。我喜欢解决方案的逻辑,但在空间考虑方面可能不是最佳的。但是我同意它很漂亮并且非常兼容跨浏览器。使用扩展运算符是有代价的,因为它适用于集合,但从长远来看,我可能会接受它而不是返回四个数组。
    【解决方案4】:

    spread 运算符允许在需要多个参数(用于函数调用)或多个元素(用于数组字面量)的地方扩展表达式。

    a2 = [21,22];
    a1 = [1,2,...a2,3,4,5];//...a2 is use of spread operator
    console.log(a1);

    【讨论】:

      【解决方案5】:

      对于这个问题,这里有一些真正有创意的答案。对于刚开始使用数组的人来说,这是一个简单的解决方案。如果需要,它可以一直工作到兼容 ECMAScript 3 的浏览器。

      在开始之前了解一些关于拼接的知识。

      Mozilla Developer Network: Array.prototype.splice()

      首先,了解.splice()的两种重要形式。

      let a1 = [1,2,3,4],
          a2 = [1,2];
      

      方法 1) 从所需索引开始删除 x (deleteCount) 个元素。

      let startIndex = 0, 
          deleteCount = 2;
      
      a1.splice(startIndex, deleteCount); // returns [1,2], a1 would be [3,4]
      

      方法 2) 删除数组末尾所需起始索引之后的元素。

      a1.splice(2); // returns [3,4], a1 would be [1,2]
      

      使用.splice(),目标可以是使用上述两种形式之一将a1 拆分为头和尾数组。

      使用方法#1,返回值将成为头部,a1 是尾部。

      let head = a1.splice(startIndex, deleteCount); // returns [1,2], a1 would be [3,4]
      

      现在,一举将头部、身体 (a2) 和尾部连接起来

      [].concat(head, a2, a1);
      

      因此,与迄今为止提出的任何其他解决方案相比,此解决方案更像现实世界。这不是你会用乐高积木做的吗? ;-) 这是一个函数,使用方法 #2 完成。

      /**
      *@param target Array The array to be split up into a head and tail.
      *@param body Array The array to be inserted between the head and tail.
      *@param startIndex Integer Where to split the target array.
      */
      function insertArray(target, body, startIndex)
      {
          let tail = target.splice(startIndex); // target is now [1,2] and the head
          return [].concat(target, body, tail);
      }
      
      let newArray = insertArray([1, 2, 3, 4], ["a", "b"], 2); // [1, 2, "a", "b", 3, 4]
      

      更短:

      /**
      *@param target Array The array to be split up into a head and tail.
      *@param body Array The array to be inserted between the head and tail.
      *@param startIndex Integer Where to split the target array.
      */
      function insertArray(target, body, startIndex)
      {
          return [].concat(target, body, target.splice(startIndex));
      }
      

      更安全:

      /**
      *@param target Array The array to be split up into a head and tail.
      *@param body Array The array to be inserted between the head and tail.
      *@param startIndex Integer Where to split the target array.
      *@throws Error The value for startIndex must fall between the first and last index, exclusive.
      */
      function insertArray(target, body, startIndex)
      {
          const ARRAY_START = 0,
                ARRAY_END = target.length - 1,
                ARRAY_NEG_END = -1,
                START_INDEX_MAGNITUDE = Math.abs(startIndex);
      
          if (startIndex === ARRAY_START) {
              throw new Error("The value for startIndex cannot be zero (0).");
          }
      
          if (startIndex === ARRAY_END || startIndex === ARRAY_NEG_END) {
              throw new Error("The startIndex cannot be equal to the last index in target, or -1.");
          }
      
          if (START_INDEX_MAGNITUDE >= ARRAY_END) {
              throw new Error("The absolute value of startIndex must be less than the last index.");
          }
      
          return [].concat(target, body, target.splice(startIndex));
      }
      

      此解决方案的优点包括:

      1) 一个简单的前提支配着解决方案——填充一个空数组。

      2) 头部、身体和尾部的命名感觉很自然。

      3) 没有对.slice() 的双重调用。完全没有切片。

      4) 没有.apply()。非常不必要。

      5) 避免了方法链接。

      6) 只需使用 var 而不是 letconst,即可在 ECMAScript 3 和 5 中工作。

      **7) 与提出的许多其他解决方案不同,确保有头部和尾部可以拍打在身体上。如果您在边界之前或之后添加数组,您至少应该使用.concat()!!!!

      注意:使用扩展运算符 ... 可以更轻松地完成所有这些操作。

      【讨论】:

        【解决方案6】:

        我想找到一种方法来使用splice() 而不是迭代:http://jsfiddle.net/jfriend00/W9n27/

        a1 = [1,2,3,4,5];
        a2 = [21,22];
        
        a2.unshift(2, 0);          // put first two params to splice onto front of array
        a1.splice.apply(a1, a2);   // pass array as arguments parameter to splice
        console.log(a1);           // [1, 2, 21, 22, 3, 4, 5];
        

        通用函数形式:

        function arrayInsertAt(destArray, pos, arrayToInsert) {
            var args = [];
            args.push(pos);                           // where to insert
            args.push(0);                             // nothing to remove
            args = args.concat(arrayToInsert);        // add on array to insert
            destArray.splice.apply(destArray, args);  // splice it in
        }
        

        【讨论】:

        • 这也修改了a2 数组,这可能是非常不可取的。此外,当您在a1a2 上拥有切片功能时,您无需转到Array.prototype
        • 我知道它正在修改 a2。 OP 可以决定这是否有问题。去原型获取方法有什么问题吗?这对我来说似乎更合适,因为我只想要该方法,并且该对象被添加到 apply() 方法中。
        • 嗯,不,它是完全相同的功能,但一个更短:Array.prototype.splice vs a1.splice :)
        • 添加通用数组插入功能。
        • .concat 返回一个新数组,它不会像splice 那样修改现有数组。应该是args = args.concat(arrayToInsert);
        【解决方案7】:
        var a1 = [1,2,3,4,5];
        var a2 = [21,22];
        
        function injectAt(d, a1, a2) {
            for(var i=a1.length-1; i>=d; i--) {
                a1[i + a2.length] = a1[i];
            }
            for(var i=0; i<a2.length; i++) {
                a1[i+d] = a2[i];
            }
        }
        
        injectAt(2, a1, a2);
        
        alert(a1);
        

        【讨论】:

          【解决方案8】:

          这是我没有特殊技巧的版本:

          function insert_array(original_array, new_values, insert_index) {
              for (var i=0; i<new_values.length; i++) {
                  original_array.splice((insert_index + i), 0, new_values[i]);
              }
              return original_array;
          }
          

          【讨论】:

          • 在这种情况下,只使用 .reverse() new_values 数组可能更简单,而不是为每次迭代递增 insert_index。当然,当 start_index 为零或 start_index - 1 时,这种解决方案的效率很差,与仅使用 .concat() 没有任何显式循环,或 unshift() 或 push()` 与 .apply() 相比。
          【解决方案9】:

          如果您想在不创建新数组的情况下将另一个数组插入到数组中,最简单的方法是使用pushunshiftapply

          例如:

          a1 = [1,2,3,4,5];
          a2 = [21,22];
          
          // Insert a1 at beginning of a2
          a2.unshift.apply(a2,a1);
          // Insert a1 at end of a2
          a2.push.apply(a2,a1);
          

          这是可行的,因为pushunshift 都采用可变数量的参数。 奖励,您可以轻松选择从哪一端附加阵列!

          【讨论】:

          • 只需通过a1.concat(a2)a2.concat(a1) 我设计的东西比你建议的更容易。但是,问题在于在数组边界之间插入一个数组,而不是专门将一个数组添加到开头或结尾。 ;-)
          【解决方案10】:

          正如另一个线程中提到的,上面的答案不适用于非常大的数组(200K 元素)。在此处查看涉及拼接和手动推送的替代答案:https://stackoverflow.com/a/41465578/1038326

          Array.prototype.spliceArray = function(index, insertedArray) {
              var postArray = this.splice(index);
              inPlacePush(this, insertedArray);
              inPlacePush(this, postArray);
          
              function inPlacePush(targetArray, pushedArray) {
            // Not using forEach for browser compatability
                  var pushedArrayLength = pushedArray.length;
                  for (var index = 0; index < pushedArrayLength; index++) {
                     targetArray.push(pushedArray[index]);
                 }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2014-05-01
            • 1970-01-01
            • 2020-11-22
            • 2013-04-11
            • 2015-09-19
            • 2011-08-09
            • 2014-04-04
            • 2019-04-14
            • 1970-01-01
            相关资源
            最近更新 更多