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.