Come funziona sort () di Javascript?


95

In che modo il codice seguente ordina questo array in ordine numerico?

var array=[25, 8, 7, 41]

array.sort(function(a,b){
  return a - b
})

So che se il risultato del calcolo è ...

Minore di 0 : "a" è ordinato per essere un indice inferiore a "b".
Zero: "a" e "b" sono considerati uguali e non viene eseguito alcun ordinamento.
Maggiore di 0: "b" viene ordinato in modo da essere un indice inferiore a "a".

La funzione di callback per l'ordinamento dell'array viene chiamata molte volte nel corso dell'ordinamento?

In tal caso, mi piacerebbe sapere quali due numeri vengono passati ogni volta nella funzione. Ho pensato che prima ci fossero voluti "25" (a) e "8" (b), seguiti da "7" (a) e "41" (b), quindi:

25 (a) - 8 (b) = 17 (maggiore di zero, quindi ordina "b" con un indice inferiore a "a"): 8, 25

7 (a) - 41 (b) = -34 (minore di zero, quindi ordina "a" in modo che sia un indice inferiore a "b": 7, 41

Come vengono quindi ordinate le due serie di numeri l'una rispetto all'altra?

Per favore aiuta un principiante in difficoltà!


5
Spero che questo abbia un senso confuso!
cw84

Risposte:


51

La funzione di callback per l'ordinamento dell'array viene chiamata molte volte nel corso dell'ordinamento?

In tal caso, mi piacerebbe sapere quali due numeri vengono passati ogni volta nella funzione

Potresti scoprire te stesso con:

array.sort((a,b) => {
  console.log(`comparing ${a},${b}`);
  return a > b ? 1
               : a === b ? 0 
                         : -1;
});

MODIFICARE

Questo è l'output che ho:

25,8
25,7
8,7
25,41

8
piuttosto fare un console.log per firebug o html DIV elemento .innerHTML + = "confronto" + a + "," + b + "\ n";
Joshua,

2
Ricorda che questo è un sito simile a wiki e possiamo modificare altre risposte per renderle migliori :)
Pablo Fernandez

7
Solo una nota per la nuova sintassi ES6: array.sort((a,b) => a - b);è una sintassi valida
Sterling Archer

1
se la funzione di ordinamento restituisce -ve allora si scambia e + ve quindi non si scambia ??
Mahi

2
@ShekharReddy puoi ancora usare gli operatori per confrontare. Ho aggiornato la risposta.
OscarRyz

44

L'interprete JavaScript ha una sorta di implementazione dell'algoritmo di ordinamento incorporata. Chiama la funzione di confronto un certo numero di volte durante l'operazione di ordinamento. Il numero di volte in cui la funzione di confronto viene chiamata dipende dal particolare algoritmo, dai dati da ordinare e dall'ordine in cui si trova prima dell'ordinamento.

Alcuni algoritmi di ordinamento hanno prestazioni scadenti su elenchi già ordinati perché li induce a fare molti più confronti rispetto al caso tipico. Altri gestiscono bene gli elenchi preordinati, ma hanno altri casi in cui possono essere "indotti" a ottenere prestazioni scadenti.

Ci sono molti algoritmi di ordinamento di uso comune perché nessun singolo algoritmo è perfetto per tutti gli scopi. I due più usati per l'ordinamento generico sono Quicksort e merge sort . Quicksort è spesso il più veloce dei due, ma l'ordinamento di unione ha alcune proprietà interessanti che possono renderlo una scelta complessiva migliore. L'ordinamento di unione è stabile , mentre Quicksort non lo è. Entrambi gli algoritmi sono parallelizzabili, ma il modo in cui funziona il merge sort rende un'implementazione parallela più efficiente, a parità di tutto il resto.

Il tuo particolare interprete JavaScript può utilizzare uno di questi algoritmi o qualcos'altro completamente. Lo standard ECMAScript non specifica quale algoritmo deve utilizzare un'implementazione conforme. Nega persino esplicitamente la necessità di stabilità.


1
FWIW, Quicksort di base è uno degli algoritmi che possono essere "indotti" per ottenere prestazioni scadenti. Nella forma semplice, ha prestazioni O (N ^ 2) per elenchi che sono già ordinati o esattamente invertiti. La maggior parte degli algoritmi Quicksort della libreria ha una serie di ottimizzazioni non ovvie che aiutano a evitare questi scenari peggiori comuni.
Adisak,

3
JavaScriptCore utilizza in realtà un albero AVL per l'ordinamento poiché è necessario comportarsi in modo deterministico di fronte alle funzioni di confronto che modificano l'array da ordinare.
olliej

Lo standard ECMAScript ora richiede stabilità .
ggorlen

11

Le coppie di valori vengono confrontate, una coppia alla volta. Le coppie che vengono confrontate sono un dettaglio di implementazione: non dare per scontato che saranno le stesse su ogni browser. Il callback può essere qualsiasi cosa (quindi puoi ordinare stringhe o numeri romani o qualsiasi altra cosa in cui puoi trovare una funzione che restituisce 1,0, -1).

Una cosa da tenere a mente con l'ordinamento di JavaScript è che non è garantito che sia stabile.


5

La funzione di callback per l'ordinamento dell'array viene chiamata molte volte nel corso dell'ordinamento?

Sì, è esattamente così. Il callback viene utilizzato per confrontare coppie di elementi nell'array in base alle necessità per determinare in quale ordine dovrebbero essere. L'implementazione della funzione di confronto non è atipica quando si tratta di un ordinamento numerico. Dettagli in spec o su alcuni altri più leggibili siti.


4

La funzione di callback per l'ordinamento dell'array viene chiamata molte volte nel corso dell'ordinamento?

Poiché questo è un ordinamento di confronto, dati N elementi, la funzione di callback dovrebbe essere invocata in media (N * Lg N) volte per un ordinamento veloce come Quicksort . Se l'algoritmo utilizzato è qualcosa come Bubble Sort , la funzione di callback verrà richiamata in media (N * N) volte.

Il numero minimo di invocazioni per un ordinamento di confronto è (N-1) e questo serve solo per rilevare un elenco già ordinato (ad esempio, in anticipo in Bubble Sort se non si verificano scambi).


3

Profondamente conoscenza

Se il risultato è negativo a viene ordinato prima di b.

Se il risultato è positivo b viene ordinato prima di a.

Se il risultato è 0, non vengono apportate modifiche all'ordinamento dei due valori.

NOTA:

Questo codice è la visualizzazione passo dopo passo all'interno del metodo di ordinamento .

PRODUZIONE:

let arr = [90, 1, 20, 14, 3, 55];
var sortRes = [];
var copy = arr.slice();		//create duplicate array
var inc = 0;	//inc meant increment
copy.sort((a, b) => {
	sortRes[inc] = [ a, b, a-b ];
	inc += 1;
	return a - b;
});
var p = 0;
for (var i = 0; i < inc; i++) {
	copy = arr.slice();
	copy.sort((a, b) => {
		p += 1;
		if (p <= i ) {
			return a - b;
		}
		else{
			return false;
		}
	});
	p = 0;
	console.log(copy +' \t a: '+ sortRes[i][0] +' \tb: '+ sortRes[i][1] +'\tTotal: '+ sortRes[i][2]);
}


0

eseguire questo codice. Puoi vedere l'esatto processo di ordinamento passo passo dall'inizio alla fine.

var array=[25, 8, 7, 41]
var count = 1;
array.sort( (a,b) => { 
console.log(`${count++}). a: ${a} | b: ${b}`);
return a-b;
});
console.log(array);

copiato e incollato sulla console ed è appena tornato indefinito.
iPzard

0

Per aiutare a chiarire il comportamento di Array#sorte il suo comparatore, considera questo ingenuo tipo di inserzione insegnato all'inizio dei corsi di programmazione:

const sort = arr => {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j && arr[j-1] > arr[j]; j--) {
      [arr[j], arr[j-1]] = [arr[j-1], arr[j]];
    }
  }
};

const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array);
console.log("" + array);

Ignorando la scelta di inserzione come algoritmo, fuoco sulla hardcoded comparatore: arr[j-1] > arr[j]. Questo ha due problemi rilevanti per la discussione:

  1. L' >operatore viene invocato su coppie di elementi dell'array ma molte cose che potresti voler ordinare come gli oggetti non rispondono >in modo ragionevole (lo stesso sarebbe vero se usassimo- ).
  2. Anche se stai lavorando con i numeri, spesso vuoi un arrangiamento diverso da quello crescente che è stato incorporato qui.

Possiamo risolvere questi problemi aggiungendo un comparefnargomento con cui hai familiarità:

const sort = (arr, comparefn) => {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
      [arr[j], arr[j-1]] = [arr[j-1], arr[j]];
    }
  }
};

const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array, (a, b) => a - b);
console.log("" + array);

sort(array, (a, b) => b - a);
console.log("" + array);

const objArray = [{id: "c"}, {id: "a"}, {id: "d"}, {id: "b"}];
sort(objArray, (a, b) => a.id.localeCompare(b.id));
console.log(JSON.stringify(objArray, null, 2));

Ora l'ingenua routine di ordinamento è generalizzata. Puoi vedere esattamente quando viene richiamato questo callback, rispondendo alla tua prima serie di dubbi:

La funzione di callback per l'ordinamento dell'array viene chiamata molte volte nel corso dell'ordinamento? In tal caso, mi piacerebbe sapere quali due numeri vengono passati ogni volta nella funzione

L'esecuzione del codice seguente mostra che, sì, la funzione viene chiamata molte volte e puoi usare console.logper vedere quali numeri sono stati passati:

const sort = (arr, comparefn) => {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
      [arr[j], arr[j-1]] = [arr[j-1], arr[j]];
    }
  }
};

console.log("on our version:");
const array = [3, 0, 4, 5];
sort(array, (a, b) => console.log(a, b) || (a - b));
console.log("" + array);

console.log("on the builtin:");
console.log("" + 
  [3, 0, 4, 5].sort((a, b) => console.log(a, b) || (a - b))
);

Tu chiedi:

Come vengono quindi ordinate le due serie di numeri l'una rispetto all'altra?

Per essere precisi con la terminologia, ae bnon sono insiemi di numeri: sono oggetti nell'array (nel tuo esempio, sono numeri).

La verità è che non importa come vengono ordinati perché dipende dall'implementazione. Se avessi utilizzato un algoritmo di ordinamento diverso dall'ordinamento per inserzione, il comparatore verrebbe probabilmente invocato su coppie di numeri diverse, ma alla fine della chiamata di ordinamento, l'invariante che conta per il programmatore JS è che la matrice dei risultati è ordinata in base al comparatore, assumendo che il comparatore restituisca valori che aderiscono al contratto che hai dichiarato (<0 quando a < b, 0 quando a === be> 0 quando a > b).

Nello stesso senso in cui ho la libertà di cambiare l'implementazione del mio tipo fintanto che non infrango la mia specifica, le implementazioni di ECMAScript sono libere di scegliere l'implementazione dell'ordinamento entro i confini della specifica del linguaggio , quindi Array#sortprobabilmente produrrà diverse chiamate di confronto su motori diversi. Non si scriverebbe codice in cui la logica si basa su una particolare sequenza di confronti (né il comparatore dovrebbe produrre effetti collaterali in primo luogo).

Ad esempio, il motore V8 (al momento della scrittura) richiama Timsort quando l'array è più grande di un certo numero di elementi precalcolati e utilizza un ordinamento per inserimento binario per piccoli blocchi di array. Tuttavia, utilizzava quicksort che è instabile e probabilmente darebbe una diversa sequenza di argomenti e chiamate al comparatore.

Poiché diverse implementazioni di ordinamento utilizzano il valore di ritorno della funzione di confronto in modo diverso, ciò può portare a un comportamento sorprendente quando il comparatore non aderisce al contratto. Vedi questo thread per un esempio.


-2
var array=[25, 8, 7, 41]

array.sort(function(a,b){
  console.log(`a = ${a} , b = ${b}`);
  return a - b
});

PRODUZIONE

  • a = 8, b = 25
  • a = 7, b = 8
  • a = 41, b = 7
  • a = 41, b = 8
  • a = 41, b = 25

nel mio browser (Google Chrome versione 70.0.3538.77 (versione ufficiale) (64 bit)) nella prima iterazione, l'argomento a è il secondo elemento in un array e l'argomento b è il primo elemento di un array.

se la funzione Confronta ritorna

  1. Il valore negativo di b viene spostato in avanti in a.
  2. Valore positivo di a viene spostato in avanti in b.
  3. 0 (Zero) sia a che b rimarranno come sono.
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.