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;
}
samccone
fonte
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];
maerics
fonte
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.

Frank Tan
fonte
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.

mgiuca
fonte
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]
uroslatos
fonte
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
*/
Kennebec
fonte
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

John Klimov
fonte
Ó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]
bjornd
fonte
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]

Miguel Mota
fonte
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.

rehman_00001
fonte