Javascript - вставить массив внутрь другого массива

88

Каков более эффективный способ вставить массив в другой массив?

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

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

Итерация a2 с использованием splice выглядит немного ужасно с точки зрения производительности, если массив a2 большой.

Спасибо.

ic3
источник
1
взгляните на stackoverflow.com/questions/586182/…
Endophage
2
это не один элемент, а массив, поэтому
сварка

Ответы:

180

Вы можете использовать в 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);
Nickf
источник
10
@icCube: Я только что провел тест, сравнив этот метод с тем, что в ответе Патрика. Он начинается с a1 и a2, как в примере, и вводит переменную a2 в a1 10 000 раз (так что в конечном итоге у вас есть массив из 20 005 элементов). Этот метод занял: 81 мс , метод slice + concat - 156 мс (проверено в Chrome 13) . Конечно, здесь есть стандартная оговорка, что в большинстве случаев четкость будет важнее скорости.
nickf
3
@nick: в хроме, если массивы содержат более 130000 элементов, applyвызовет исключение stackoverflow. jsfiddle.net/DcHCY Также происходит в jsPerf Я считаю, что в FF и IE порог составляет около 500 тыс.
Нет,
почему вы используете apply вместо прямого вызова splice и передачи аргументов?
Питер П.
@ FrançoisWahl, это серьезная проблема, которую люди часто игнорируют в своих ответах. Я взял ваш пример и заставил его работать с северными элементами 1M: stackoverflow.com/a/41466395/1038326
Габриэль Коэн
Я только что попробовал это (июнь 2017 г.), и самый быстрый способ как для небольших, так и для больших размеров массивов (в Chrome, FF и Edge), кажется, target.slice(0, insertIndex).concat(insertArr, target.slice(insertIndex)); jsperf.com/inserting-an-array-within-an-array
Alex Дима
14

Сначала ошибся. Надо было использовать 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 );
user113716
источник
Я получаю a1 с 6 элементами, а не с 7 -> [1,2, [21,22], 3,4,5]
ic3
работает ! Большое спасибо, давайте подождем несколько дней, но это лучший ответ ... странно, у них нет функции js для этого.
ic3
Мне больше нравится этот
Kimchi Man
В общем, JS-движок должен вернуть четыре массива, прежде чем выражение будет завершено. Мне нравится логика решения, но оно может быть не оптимальным с точки зрения занимаемой площади. Однако я согласен с тем, что он отличный и очень кроссбраузерный. Использование оператора распространения требует определенных затрат, поскольку он работает с коллекциями, но я мог бы взять на себя это возвращение четырех массивов в долгосрочной перспективе.
Энтони Ратледж
3

Оператор распространения позволяет расширять выражение в местах, где ожидается несколько аргументов (для вызовов функций) или нескольких элементов (для литералов массива).

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

Хикмат Сиджапати
источник
3

На этот вопрос есть несколько действительно творческих ответов. Вот простое решение для тех, кто только начинает работать с массивами. При желании его можно заставить работать вплоть до браузеров, совместимых с ECMAScript 3.

Прежде чем начать, узнайте кое-что о сварке.

Сеть разработчиков Mozilla: 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) Работает в ECMAScript 3 и 5, просто используя varвместо letили const.

** 7) Гарантирует, что голову и хвост можно будет хлопнуть по телу, в отличие от многих других представленных решений. Если вы добавляете массив до или после границ, вы должны хотя бы использовать .concat()!!!!

Примечание. Использование оператора разбрасывания ...значительно упрощает выполнение всего этого.

Энтони Ратледж
источник
2

Я хотел найти способ сделать это 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
}
jfriend00
источник
Это также изменяет a2массив, что, вероятно, весьма нежелательно. Кроме того, вам не нужно переходить к, Array.prototypeкогда у вас есть функция среза прямо там a1или a2.
nickf
Я знал, что это модификация a2. OP может решить, проблема это или нет. Есть ли что-нибудь плохое в том, чтобы обратиться к прототипу за методом? Мне это показалось более подходящим, так как мне просто нужен был метод, а объект добавлялся в apply()метод.
jfriend00
Ну нет, это точно такая же функция, но одна короче: Array.prototype.splicevs a1.splice:)
nickf
Добавлена ​​функция вставки массива общего назначения.
jfriend00
.concatвозвращает новый массив, он не изменяет существующий, как splice. Должно бытьargs = args.concat(arrayToInsert);
nickf
0
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);
Бхеш Гурунг
источник
0

Вот моя версия без особых ухищрений:

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;
}
Arlomedia
источник
В этом случае может быть проще просто .reverse () массив new_values ​​вместо увеличения insert_index для каждой итерации. Конечно, когда start_index равен нулю или start_index - 1, эффективность этого решения низка по сравнению с простым использованием .concat () без каких-либо явных циклов или unshift () или push () `с .apply().
Энтони Ратледж
0

Если вы хотите вставить другой массив в массив, не создавая новый, самый простой способ - использовать либо, pushлибо unshiftсapply

Например:

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);

Это работает , потому как pushи unshiftпринимать переменное число аргументов. Бонус, вы можете легко выбрать, с какого конца прикрепить массив!

RangerMauve
источник
Просто сделав a1.concat(a2)или a2.concat(a1)я придумал что-то более легкое, чем то, что вы предлагаете. Однако проблема заключается во вставке массива между границами массива, а не только в добавлении массива в начало или конец. ;-)
Энтони Ратледж
0

Как упоминалось в другом потоке, приведенные выше ответы не будут работать в очень больших массивах (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]);
       }
    }
}
Габриэль Коэн
источник