Il modo più efficiente di anteporre un valore a un array


268

Supponendo che io abbia un array di dimensioni N(dove N > 0), esiste un modo più efficiente di anteporre all'array che non richiederebbe passaggi O (N + 1)?

Nel codice, essenzialmente, quello che sto facendo attualmente è

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

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

  return newArray;
}

per quanto adoro gli elenchi e i puntatori collegati, mi sembra che ci debba essere un modo più efficace per fare le cose usando i tipi di dati JS nativi
samccone

@samccone: Sì, ignora il mio commento, scusa, pensavo avessi detto Java: P
GWW,

3
Java, JavaScript, C o Python, non importa quale lingua: il compromesso di complessità tra array e liste collegate è lo stesso. Gli elenchi collegati sono probabilmente abbastanza ingombranti in JS perché non esiste una classe integrata per loro (a differenza di Java), ma se quello che vuoi davvero è il tempo di inserimento O (1), allora vuoi un elenco collegato.
mgiuca,

1
È necessario clonarlo?
Ryan Florence,

1
Se è un requisito per clonarlo, la non spostamento è inappropriato, poiché cambierà l'array originale e non ne creerà una copia.
mgiuca,

Risposte:


493

Non sono sicuro di più efficiente in termini di big-O, ma sicuramente l'utilizzo del unshiftmetodo è più conciso:

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

[Modificare]

Questo benchmark jsPerf mostra che unshiftè decentemente più veloce in almeno un paio di browser, indipendentemente dalle prestazioni big-O eventualmente diverse se si è d'accordo con la modifica dell'array sul posto. Se davvero non puoi mutare l'array originale, dovresti fare qualcosa come lo snippet di seguito, che non sembra essere notevolmente più veloce della tua soluzione:

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

[Modifica 2]

Per completezza, è possibile utilizzare la seguente funzione invece dell'esempio di OP prependArray(...)per sfruttare il unshift(...)metodo 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
Chi ha deciso di chiamare prepend" unshift"?
Scott Stafford,

12
@ScottStafford unshiftsembra più appropriato per una tale operazione di array (sposta gli elementi ... più o meno fisicamente). prependsarebbe più appropriato per gli elenchi collegati, in cui si antepongono letteralmente elementi.
CamilB,

12
sbloccare è la funzione complementare da spostare. Chiamarlo "anteporre" sarebbe la scelta strana. Unshift deve spostarsi mentre la spinta è pop.
Sir Robert,

9
Solo un problema con questa soluzione. Unshift () non restituisce la sua lunghezza e non l'array come in questa risposta? w3schools.com/jsref/jsref_unshift.asp
iDVB

4
pushed unshiftentrambi contengono u, mentre pope shiftnon hanno.
Qian Chen,

70

Con ES6, ora è possibile utilizzare l' operatore spread per creare un nuovo array con i nuovi elementi inseriti prima degli elementi originali.

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

Aggiornamento 17-08-2018: prestazioni

Intendevo questa risposta per presentare una sintassi alternativa che ritengo più memorabile e concisa. Va notato che secondo alcuni parametri di riferimento (vedere questa altra risposta ), questa sintassi è significativamente più lenta. Questo probabilmente non avrà importanza se non si eseguono molte di queste operazioni in un ciclo.


4
Questo dovrebbe essere votato a partire dal 2017. È conciso. Utilizzando spread, restituisce il nuovo stream, che può essere utile per concatenare ulteriori modifiche. È anche puro (nel senso che aeb non sono interessati)
Simon

Non hai parlato del tempo impiegato rispetto aunshift
Shashank Vivek,

Grazie @ShashankVivek. Intendevo questa risposta per presentare una sintassi alternativa che ritengo più memorabile e concisa. Aggiornerò
Frank Tan,

@FrankTan: Cheers :) +1
Shashank Vivek

46

Se si sta anteponendo un array alla parte anteriore di un altro array, è più efficiente utilizzarlo concat. Così:

var newArray = values.concat(oldArray);

Ma questo sarà ancora O (N) delle dimensioni di oldArray. Tuttavia, è più efficiente dell'iterazione manuale su oldArray. Inoltre, a seconda dei dettagli, può esserti utile, perché se hai intenzione di anteporre molti valori, è meglio inserirli prima in un array e poi concatenare oldArray alla fine, piuttosto che anteporre ciascuno singolarmente.

Non c'è modo di fare meglio di O (N) nella dimensione di oldArray, perché le matrici sono memorizzate nella memoria contigua con il primo elemento in una posizione fissa. Se si desidera inserire prima del primo elemento, è necessario spostare tutti gli altri elementi. Se hai bisogno di un modo per aggirare questo, fai quello che ha detto @GWW e usa un elenco collegato o una diversa struttura di dati.


2
Oh sì, me ne sono dimenticato unshift. Ma nota che a) che muta oldArray mentre concatnon lo fa (quindi quale è meglio per te dipende dalla situazione) eb) inserisce solo un elemento.
mgiuca,

1
Wow, è molto più lento. Bene, come ho detto, sta facendo una copia dell'array (e sta anche creando un nuovo array [0]), mentre il non spostamento lo sta mutando sul posto. Ma entrambi dovrebbero essere O (N). Applausi anche per il link a quel sito - sembra molto utile.
mgiuca,

2
va bene per gli oneliner, dato che concat restituisce l'array e unshift restituisce la nuova lunghezza
Z. Khullah

32

Se si desidera anteporre l'array (a1 con un array a2) è possibile utilizzare quanto segue:

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

6

Se è necessario preservare il vecchio array, tagliare il vecchio e spostare i nuovi valori all'inizio della sezione.

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

Ho alcuni nuovi test su diversi metodi di prepending. Per array di piccole dimensioni (<1000 elems) il leader è per il ciclo accoppiato con un metodo push. Per matrici enormi, il metodo Unshift diventa il leader.

Ma questa situazione è effettiva solo per il browser Chrome. In Firefox unshift ha una fantastica ottimizzazione ed è più veloce in tutti i casi.

La diffusione ES6 è 100 volte più lenta in tutti i browser.

https://jsbench.me/cgjfc79bgx/1


Bella risposta! Ma per assicurarti di non perderne una parte, potresti riprodurre qui i tuoi casi di test (magari con alcuni risultati di esempio) nel caso, ad esempio, jsbench scompaia.
ruffin,

3

C'è un metodo speciale:

a.unshift(value);

Ma se vuoi anteporre diversi elementi all'array, sarebbe più veloce usare un metodo del genere:

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
No ... unshift aggiungerà array come primo argomento: var a = [4, 5]; a.unshift ([1,2,3]); console.log (a); // -> [[4, 5], 1, 2, 3]
bjornd

4
Hai ragione! Dovresti usareArray.prototype.unshift.apply(a,b);
David

1

Esempio di prepending sul posto:

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

A.unshift(...B)

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


1

La chiamata unshiftrestituisce solo la lunghezza del nuovo array. Quindi, per aggiungere un elemento all'inizio e restituire un nuovo array, ho fatto questo:

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

o semplicemente con l'operatore spread:

[ newVal, ...array ]

In questo modo, l'array originale rimane intatto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.