A maneira mais eficiente de acrescentar um valor a uma matriz


268

Supondo que eu tenha uma matriz que tenha um tamanho de N(onde N > 0), existe uma maneira mais eficiente de preceder a matriz que não exigiria etapas O (N + 1)?

No código, essencialmente, o que estou fazendo atualmente é

function prependArray(value, oldArray) {
  var newArray = new Array(value);

  for(var i = 0; i < oldArray.length; ++i) {
    newArray.push(oldArray[i]);
  }

  return newArray;
}

tanto quanto eu amo listas ligadas e ponteiros eu sinto como se deve haver uma maneira mais eficaz de fazer as coisas usando tipos de dados nativos JS
— samccone

@samccone: Sim, desconsidere meu comentário, desculpe, eu pensei que você dissesse Java: P
— GWW

3
Java, JavaScript, C ou Python, não importa qual idioma: a troca de complexidade entre matrizes versus listas vinculadas é a mesma. As Listas Vinculadas provavelmente são bastante pesadas em JS, porque não há uma classe interna para elas (ao contrário do Java), mas se o que você realmente deseja é o tempo de inserção O (1), então deseja uma lista vinculada.
— mgiuca

1
É um requisito cloná-lo?
— Ryan Florence

1
Se for um requisito cloná-lo, o desvio é inadequado, pois ele modifica a matriz original e não cria uma cópia.
— mgiuca

Respostas:


493

Não tenho certeza de que seja mais eficiente em termos de big-O, mas certamente o uso do unshiftmétodo é mais conciso:

var a = [1, 2, 3, 4];
a.unshift(0);
a; // => [0, 1, 2, 3, 4]

[Editar]

Esse benchmark jsPerf mostra que unshifté decentemente mais rápido em pelo menos alguns navegadores, independentemente do desempenho de grande O possivelmente diferente, se você estiver certo em modificar a matriz no local. Se você realmente não pode alterar a matriz original, faria algo como o snippet abaixo, que não parece ser apreciavelmente mais rápido que sua solução:

a.slice().unshift(0); // Use "slice" to avoid mutating "a".

[Editar 2]

Para completar, a seguinte função pode ser usada em vez do exemplo do OP prependArray(...)para tirar proveito do unshift(...)método Array :

function prepend(value, array) {
  var newArray = array.slice();
  newArray.unshift(value);
  return newArray;
}

var x = [1, 2, 3];
var y = prepend(0, x);
y; // => [0, 1, 2, 3];
x; // => [1, 2, 3];

190
Quem decidiu ligar para prepend" unshift"?
— Scott Stafford

12
O @ScottStafford unshiftparece mais apropriado para essa operação de matriz (move os elementos ... mais ou menos fisicamente). prependseria mais apropriado para listas vinculadas, nas quais você literalmente anexa elementos.
— CamilB

12
Desvio é a função complementar de mudança. Chamá-lo de "prefpend" seria uma escolha estranha. Desshift é mudar como push é pop.
— Sir Robert

9
Apenas um problema com esta solução. Unshift () não retorna seu comprimento , e não a matriz, como nesta resposta? w3schools.com/jsref/jsref_unshift.asp
— iDVB

4
pushe unshiftambos contêm u, enquanto pope shiftnão têm.
— Qian Chen

70

Com o ES6, agora você pode usar o operador de spread para criar uma nova matriz com seus novos elementos inseridos antes dos elementos originais.

// Prepend a single item.
const a = [1, 2, 3];
console.log([0, ...a]);

// Prepend an array.
const a = [2, 3];
const b = [0, 1];
console.log([...b, ...a]);

Atualização 17/08/2018: Desempenho

Pretendi que esta resposta apresentasse uma sintaxe alternativa que eu acho mais memorável e concisa. Deve-se notar que, de acordo com alguns parâmetros de referência (consulte esta outra resposta ), essa sintaxe é significativamente mais lenta. Provavelmente isso não será importante, a menos que você esteja executando muitas dessas operações em um loop.


4
Isso deve ser votado a partir de 2017. É conciso. Ao usar o spread, ele devolve o novo fluxo, que pode ser útil para encadear outras modificações. Também é puro (o que significa que a e b são afetados)
— Simon

Voc no mencionar abt o tempo gasto em comparação comunshift
— Shashank Vivek

Obrigado @ShashankVivek. Pretendi que esta resposta apresentasse uma sintaxe alternativa que eu acho mais memorável e concisa. Eu atualizarei.
— Frank Tan

@FrankTan: Cheers :) +1
— Shashank Vivek

46

Se você estiver anexando uma matriz à frente de outra matriz, é mais eficiente usar apenas concat. Assim:

var newArray = values.concat(oldArray);

Mas isso ainda será O (N) no tamanho do oldArray. Ainda assim, é mais eficiente do que iterar manualmente sobre o oldArray. Além disso, dependendo dos detalhes, isso pode ajudá-lo, porque se você anexará muitos valores, é melhor colocá-los em uma matriz primeiro e concatenar o oldArray no final, em vez de anexar cada um individualmente.

Não há como fazer melhor que O (N) no tamanho de oldArray, porque as matrizes são armazenadas na memória contígua com o primeiro elemento em uma posição fixa. Se você deseja inserir antes do primeiro elemento, é necessário mover todos os outros elementos. Se você precisar contornar isso, faça o que o @GWW disse e use uma lista vinculada ou uma estrutura de dados diferente.


2
Ah, sim, eu esqueci unshift. Mas observe que a) que modifica oldArray enquanto concatque não (então qual é o melhor para você depende da situação) eb) ele insere apenas um elemento.
— mgiuca

1
Uau, isso é muito mais lento. Bem, como eu disse, ele está fazendo uma cópia da matriz (e também criando uma nova matriz [0]), enquanto o unshift a está mutando no local. Mas ambos devem ser O (N). Também aplaude o link para esse site - parece muito útil.
— mgiuca

2
seu bom para oneliner, desde concat retorna a matriz, e retorna unshift o novo comprimento
— Z. Khullah

32

Se você deseja anexar a matriz (a1 com uma matriz a2), use o seguinte:

var a1 = [1, 2];
var a2 = [3, 4];
Array.prototype.unshift.apply(a1, a2);
console.log(a1);
// => [3, 4, 1, 2]

6

Se você precisar preservar a matriz antiga, corte a matriz antiga e desvie o (s) novo (s) valor (es) para o início da fatia.

var oldA=[4,5,6];
newA=oldA.slice(0);
newA.unshift(1,2,3)

oldA+'\n'+newA

/*  returned value:
4,5,6
1,2,3,4,5,6
*/

4

Eu tenho alguns testes novos de diferentes métodos de pré-anexação. Para matrizes pequenas (<1000 elems), o líder é para ciclo acoplado a um método push. Para matrizes enormes, o método Unshift se torna o líder.

Mas essa situação é real apenas para o navegador Chrome. No Firefox, o unshift tem uma otimização impressionante e é mais rápido em todos os casos.

A propagação do ES6 é 100 vezes mais lenta em todos os navegadores.

https://jsbench.me/cgjfc79bgx/1


Ótima resposta! Mas para garantir que você não perca parte disso, você pode reproduzir seus casos de teste aqui (talvez com alguns resultados de execução de amostra), caso, por exemplo, o jsbench desapareça.
— Ruffin

3

Existe um método especial:

a.unshift(value);

Mas se você quiser acrescentar vários elementos à matriz, seria mais rápido usar esse método:

var a = [1, 2, 3],
    b = [4, 5];

function prependArray(a, b) {
    var args = b;
    args.unshift(0);
    args.unshift(0);
    Array.prototype.splice.apply(a, args);
}

prependArray(a, b);
console.log(a); // -> [4, 5, 1, 2, 3]

2
Não ... unshift adicionará array como o primeiro argumento: var a = [4, 5]; a.shift ([1,2,3]); console.log (a); // -> [[4, 5], 1, 2, 3]
— bjornd

4
Você está certo! Você precisaria usarArray.prototype.unshift.apply(a,b);
— david

1

Exemplo de anexação no local:

var A = [7,8,9]
var B = [1,2,3]

A.unshift(...B)

console.log(A) // [1,2,3,7,8,9]


1

A chamada unshiftretorna apenas o comprimento da nova matriz. Então, para adicionar um elemento no começo e retornar uma nova matriz, fiz o seguinte:

let newVal = 'someValue';
let array = ['hello', 'world'];
[ newVal ].concat(array);

ou simplesmente com o operador spread:

[ newVal, ...array ]

Dessa forma, a matriz original permanece intocada.

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.