Javascript - insira uma matriz dentro de outra matriz


87

Qual é a maneira mais eficiente de inserir um array dentro de outro array.

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

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

Iterar a2 usando splice parece um pouco horrível do ponto de vista do desempenho se o array a2 for grande.

Obrigado.



2
não é um item, mas uma matriz, então a emenda não está funcionando
ic3

Respostas:


177

Você pode usar splicecombinado com alguns applytruques:

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

No ES2015 +, você poderia usar o operador spread em vez de tornar isso um pouco mais agradável

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

10
@icCube: Acabei de fazer um benchmark, comparando esse método com o da resposta de patrick. Ele começa com a1 e a2 como no exemplo e injeta a variável a2 em a1 10.000 vezes (de modo que, no final, você tenha uma matriz com 20.005 elementos). Este método levou: 81 ms , o método slice + concat levou 156 ms (testado no Chrome 13) . Claro, isso vem com a advertência padrão de que, na maioria dos casos, a legibilidade será mais importante do que a velocidade.
nickf

3
@nick: No chrome, se os arrays contiverem mais de 130000 itens apply, uma exceção stackoverflow será lançada . jsfiddle.net/DcHCY Também acontece no jsPerf Eu acredito no FF e no IE o limite é em torno de 500k
Não

por que você usa apply em vez de chamar splice diretamente e passar os argumentos?
Peter P.

@ FrançoisWahl, este é um problema sério que as pessoas muitas vezes ignoram em suas respostas. Peguei seu exemplo e o fiz funcionar com o norte de 1M elementos: stackoverflow.com/a/41466395/1038326
Gabriel Kohen

Acabei de tentar isso (junho de 2017) e a maneira mais rápida para tamanhos de array pequenos e grandes (no Chrome, FF e Edge) parece ser target.slice(0, insertIndex).concat(insertArr, target.slice(insertIndex)); jsperf.com/inserting-an-array-within-an-array
Alex Dima


14

Estava errado no começo. Deveria ter usado em seu concat()lugar.

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

Exemplo: http://jsfiddle.net/f3cae/1/

Esta expressão usa slice(0, 2)[docs] para retornar os dois primeiros elementos de a1(onde 0é o índice inicial e 2é o elemento deleteCount, embora a1não seja alterado).

Resultado intermediário :[1,2]

Em seguida, usa concat(a2)[docs] para anexar a2ao final do [1,2].

Resultado intermediário : [1,2,21,22].

Em seguida, a1.slice(2)é chamado dentro de um final .concat()da cauda desta expressão, que equivale a [1,2,21,22].concat(a1.slice(2)).

Uma chamada para slice(2), tendo um argumento inteiro positivo, retornará todos os elementos após o segundo elemento, contando por números naturais (como em, há cinco elementos, então [3,4,5]será retornado de a1). Outra maneira de dizer isso é que o argumento do índice de número inteiro singular informa a1.slice()em qual posição na matriz começar a retornar os elementos (o índice 2 é o terceiro elemento).

Resultado intermediário :[1,2,21,22].concat([3,4,5])

Finalmente, o segundo .concat()adiciona [3,4,5]ao final de [1,2,21,22].

Resultado :[1,2,21,22,3,4,5]

Pode ser tentador alterar Array.prototype, mas pode-se simplesmente estender o objeto Array usando a herança prototípica e injetar esse novo objeto em seus projetos.

No entanto, para aqueles que vivem no limite ...

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

Recebo a1 com 6 itens, não 7 -> [1,2, [21,22], 3,4,5]
ic3

trabalho ! muito obrigado, vamos esperar alguns dias para mas esta é a melhor resposta ... estranho eles não tem função js para isso.
ic3

Eu gosto mais deste
Kimchi Man

Em suma, o mecanismo JS deve retornar quatro matrizes antes que a expressão seja concluída. Gosto da lógica da solução, mas pode não ser a ideal em termos de considerações de espaço. No entanto, concordo que é bacana e muito compatível com vários navegadores. Há um custo para usar o operador spread, pois ele funciona em coleções, mas posso assumir isso em vez de retornar quatro arrays no longo prazo.
Anthony Rutledge,

3

O operador de propagação permite que uma expressão seja expandida em locais onde vários argumentos (para chamadas de função) ou vários elementos (para literais de matriz) são esperados.

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


3

Existem algumas respostas verdadeiramente criativas para esta pergunta aqui. Aqui está uma solução simples para quem está começando com arrays. Ele pode ser feito para funcionar em navegadores compatíveis com ECMAScript 3, se desejado.

Saiba algo sobre emenda antes de começar.

Rede de desenvolvedores Mozilla: Array.prototype.splice ()

Primeiro, compreenda duas formas importantes de .splice().

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

Método 1) Remova os elementos x (deleteCount), começando com um índice desejado.

let startIndex = 0, 
    deleteCount = 2;

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

Método 2) Remova os elementos após um índice inicial desejado até o final da matriz.

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

Usando .splice(), um objetivo poderia ser dividir a1em matrizes inicial e final usando uma das duas formas acima.

Usando o método nº 1, o valor de retorno se tornaria a cabeça e a1a cauda.

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

Agora, de uma só vez, concatene a cabeça, o corpo ( a2) e a cauda

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

Portanto, essa solução é mais parecida com o mundo real do que qualquer outra apresentada até agora. Não é isso que você faria com Legos? ;-) Aqui está uma função, feita usando o método # 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]

Mais curta:

/**
*@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));
}

Mais seguro:

/**
*@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));
}

As vantagens desta solução incluem:

1) Uma premissa simples domina a solução - preencha uma matriz vazia.

2) A nomenclatura da cabeça, corpo e cauda parece natural.

3) Nenhuma chamada dupla para .slice(). Sem fatiar.

4) Não .apply(). Altamente desnecessário.

5) O encadeamento de métodos é evitado.

6) Funciona no ECMAScript 3 e 5 simplesmente usando em varvez de letou const.

** 7) Garante que haverá cabeça e cauda para dar um tapa no corpo, ao contrário de muitas outras soluções apresentadas. Se você estiver adicionando um array antes ou depois dos limites, deve pelo menos usar .concat()!!!!

Nota: O uso do opearador de propagação ...torna tudo isso muito mais fácil de realizar.


2

Eu queria encontrar uma maneira de fazer isso com splice()e sem iteração: 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];

Na forma de função de uso geral:

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
}

Isso modifica a a2matriz também, o que provavelmente é bastante indesejável. Além disso, você não precisa ir para o Array.prototypequando tiver a função de fatia bem ali no a1ou a2.
nickf

Eu sabia que estava modificando a2. OP pode decidir se isso é um problema ou não. Há algo de errado em ir ao protótipo para obter o método? Pareceu-me mais apropriado, pois eu só queria o método e o objeto estava sendo adicionado ao apply()método.
jfriend00

Bem, não, é exatamente a mesma função, mas uma é mais curta: Array.prototype.splicevs a1.splice:)
nickf

Adicionada função de inserção de array de propósito geral.
jfriend00

.concatretorna um novo array, ele não modifica o existente como splice. Deveria serargs = 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

Esta é minha versão sem truques especiais:

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

Neste caso, pode ser mais simples apenas .reverse () o array new_values, ao invés de incrementar insert_index para cada iteração. Claro, quando start_index é zero ou start_index - 1, a eficiência desta solução é pobre, compare apenas usando .concat () sem nenhum loop explícito, ou unshift () ou push () `com .apply().
Anthony Rutledge

0

Se você deseja inserir outro array em um array sem criar um novo, a maneira mais fácil é usar pushou unshiftcomapply

Por exemplo:

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

Isso funciona porque ambos pushe unshiftrecebem um número variável de argumentos. Um bônus, você pode escolher facilmente em qual extremidade anexar a matriz!


Simplesmente fazendo a1.concat(a2)ou a2.concat(a1), criei algo mais fácil do que o que você sugere. No entanto, o problema é inserir uma matriz entre os limites da matriz, não exclusivamente adicionar uma matriz ao início ou ao fim. ;-)
Anthony Rutledge

0

Conforme mencionado em outro tópico, as respostas acima não funcionarão em matrizes muito grandes (200 mil elementos). Veja a resposta alternativa aqui envolvendo emenda e envio manual: 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]);
       }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.