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 большой.

Спасибо.



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

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

Я получаю 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
}

Это также изменяет 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;
}

В этом случае может быть проще просто .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принимать переменное число аргументов. Бонус, вы можете легко выбрать, с какого конца прикрепить массив!


Просто сделав 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]);
       }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.