Ordinamento dei numeri in ordine decrescente ma con `0`s all'inizio


36

Ho una sfida in JavaScript che sto cercando di capire già da un po '.

Considera questo array:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

Devo produrre questo risultato:

arr = [0, 0, 0, 0, 0, 5, 4, 3, 2, 1]

Sto seguendo questa linea di logica per posizionare gli zeri in primo piano, regolando il valore dell'indice:

arr.sort((x, y) => {
    if (x !== 0) {
        return 1;
    }

    if (x === 0) {
        return -1;
    }

    return y - x;
});

Ma sono bloccato a questo risultato:

arr = [0, 0, 0, 0, 0, 1, 2, 3, 4, 5]

Qualcuno ha qualche consiglio su come risolverlo?


6
È garantito che non ci siano numeri negativi?
Michael - Dov'è Clay Shirky il

2
Non risolve se si passa all'ultima riga return x - y;?
Mooing Duck,

2
Sarebbe efficiente in JavaScript contare e rimuovere gli zeri, ordinare normalmente gli elementi rimanenti? (senza un comparatore personalizzato, si spera che il motore JS possa utilizzare una funzione di ordinamento dei numeri integrata). Quindi anteporre il giusto numero di zeri al risultato. Se ci sono molti zeri, rimuoverli prima dell'ordinamento crea un problema minore. O scambiare gli zeri all'inizio dell'array con un passaggio, quindi ordinare la coda dell'array?
Peter Cordes,

2
Lo arriva mai return y - x;? Anche in JavaScript, non riesco a pensare a nulla che non sia né ===0né né !==0.
George T,

Risposte:


35

È possibile ordinare in base al delta di be a(per l'ordinamento decrescente) e prendere Number.MAX_VALUE, per valori falsi come zero.

Questo:

Number.MAX_VALUE - Number.MAX_VALUE

è uguale a zero.

let array = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

array.sort((a, b) => (b || Number.MAX_VALUE) - (a || Number.MAX_VALUE));

console.log(...array);


13
La funzione di confronto verrà restituita NaNse entrambi ae bsono zero. Questo potrebbe essere un comportamento indesiderato.
dan04

20
I risultati di Array.prototype.sortsono definiti dall'implementazione se il comparatore ritorna mai NaN, quindi questo comparatore è una cattiva idea. Cerca di essere intelligente e sbaglia.
user2357112 supporta Monica il

3
Cosa succede se un elemento all'interno dell'array è Infinity? La funzione di confronto restituisce 0 quando si confronta un numero Infinity a 0.
Marco

4
grazie a tutti. prendo il numero più grande che può essere sottratto da solo, e spero che questo numero non venga utilizzato nell'array op.
Nina Scholz,

4
Assolutamente non leggibile. Bello, ma illeggibile.
Salman,

24

Come dice mdn docs:

Se aeb sono due elementi da confrontare, quindi:

Se compareFunction(a, b) restituisce meno di 0, ordina in base aa un indice inferiore a b(ovvero, viene prima).

Se compareFunction(a, b)restituisce 0, lasciare aeb invariati l'uno rispetto all'altro, ma ordinati in base a tutti i diversi elementi. Nota: lo standard ECMAscript non garantisce questo comportamento, quindi non tutti i browser (ad esempio versioni di Mozilla risalenti almeno al 2003) lo rispettano.

Se compareFunction(a, b) restituisce maggiore di 0, ordina in base ba un indice inferiore a (ovvero bviene prima).

compareFunction(a, b)deve sempre restituire lo stesso valore quando viene fornita una coppia specifica di elementi ae bi suoi due argomenti. Se vengono restituiti risultati incoerenti, l'ordinamento non è definito.

Quindi, la funzione di confronto ha la seguente forma:

function compare(a, b) {
  if (a is less than b by some ordering criterion) {
    return -1;
  }
  if (a is greater than b by the ordering criterion) {
    return 1;
  }
  // a must be equal to b
  return 0;
}

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

arr.sort((x, y) => {
    if (x > 0 && y > 0) {
        return y - x;
    }
    return x - y;
});

console.log(arr);


3
+1 per dare quella che sembra essere l'unica soluzione finora con una funzione di confronto che è effettivamente coerente (come definito nello standard ) per tutti gli input, compresi i numeri negativi.
Ilmari Karonen,

3
@IlmariKaronen: Sfortunatamente, il confronto è coerente per i numeri negativi, ma è il confronto sbagliato per i numeri negativi. I negativi si ordinano prima di 0 con questo comparatore e si ordinano in ordine crescente.
user2357112 supporta Monica il

2
@ user2357112supportsMonica Ciononostante, OP non ha specificato il comportamento desiderato per i numeri negativi e, con questo prototipo, è banale regolare il comportamento dei numeri negativi per adattarsi a qualsiasi requisito di OP. La cosa positiva di questa risposta è che è idiomatica e usa una funzione di confronto standard per fare il lavoro.
J ...

13

Se ti interessa l'efficienza, probabilmente è più veloce prima filtrare gli zeri . Tu non vuoisort perdere tempo nemmeno a guardarli, figuriamoci aggiungendo lavoro extra al tuo callback di confronto per gestire quel caso speciale.

Soprattutto se ti aspetti un numero significativo di zeri, un passaggio sui dati per filtrarli dovrebbe essere molto meglio che fare un ordinamento O (N log N) più grande che guarderà ogni zero più volte.

Puoi anteporre in modo efficiente il giusto numero di zeri dopo aver finito.

È anche facile leggere il codice risultante. Ho usato TypedArray perché è efficiente e semplifica l'ordinamento numerico . Ma puoi usare questa tecnica con un array normale, usando il linguaggio standard di (a,b)=>a-bfor .sort.

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let nonzero_arr = Int32Array.from(arr.filter(n => n != 0));
let zcount = arr.length - nonzero_arr.length;
nonzero_arr.sort();      // numeric TypedArray sorts numerically, not alphabetically

// Reverse the sorted part before copying into the final array.
nonzero_arr.reverse();

 // efficient-ish TypedArray for main result
let revsorted = new Int32Array(arr.length);   // zero-filled full size
revsorted.set(nonzero_arr, zcount);           // copy after the right number of zeros

console.log(Array.from(revsorted));      // prints strangely for TypedArray, with invented "0", "1" keys

/*
   // regular Array result
let sorted = [...Array(zcount).fill(0), ...nonzero_arr]  // IDK if this is efficient
console.log(sorted);
*/

Non so se TypedArray .sort()e quindi .reversesia più veloce dell'utilizzo di una funzione di confronto personalizzata per ordinare in ordine decrescente. O se possiamo copiare e invertire al volo con un iteratore.


Vale anche la pena considerare: utilizzare solo un TypedArray per l'intera lunghezza .

Invece di usare .filter, passaci sopra e scambia gli zeri nella parte anteriore dell'array mentre procedi. Questo richiede un passaggio sui tuoi dati.

Quindi utilizzare .subarray() per ottenere una nuova vista TypedArray degli elementi diversi da zero dello stesso ArrayBuffer sottostante. L'ordinamento che ti lascerà l'array completo con un inizio zero e una coda ordinata, con l'ordinamento sempre e solo guardando gli elementi diversi da zero.

Non ho visto una funzione di partizione nei metodi Array o TypedArray, ma conosco a malapena JavaScript. Con una buona JIT, un ciclo non dovrebbe essere troppo peggio di un metodo integrato. (Soprattutto quando quel metodo prevede un callback simile .filtere se non lo usarealloc sotto il cofano per ridursi, deve capire quanta memoria assegnare prima di filtrare effettivamente).

Ho usato un array normale .filter() prima di convertirlo in un TypedArray. Se il tuo input è già un TypedArray non hai questo problema e questa strategia diventa ancora più attraente.


Questo può probabilmente essere più semplice, e / o più idiomatico, o almeno più compatto. IDK se i motori JS riescono a eliminare / ottimizzare gran parte della copia o inizializzare lo zero, o se sarebbe utile utilizzare i loop anziché i metodi TypedArray, ad esempio per copiare all'indietro invece di reverse + .set. Non sono andato in giro per testarlo in velocità; se qualcuno volesse farlo sarei interessato.
Peter Cordes,

Secondo i test di @ Salman questa risposta si comporta come una schifezza per array di piccole dimensioni (di ~ 1000 elementi, il 10% dei quali sono 0). Presumibilmente fa male tutta la creazione di nuovi array. O forse un mix disattento di TypedArray e regolare? La partizione sul posto all'interno di un TypedArray in un ordinamento all-zero e non-zero + subarray potrebbe essere molto migliore, come suggerito nella seconda sezione della mia risposta.
Peter Cordes,

8

Basta modificare le condizioni della funzione di confronto in questo modo -

let arr = [-1, 0, 1, 0, 2, -2, 0, 3, -3, 0, 4, -4, 0, 5, -5];
arr.sort((a, b) => {
   if(a && b) return b-a;
   if(!a && !b) return 0;
   return !a ? -1 : 1;
});

console.log(arr);


6
Questo non è un comparatore coerente se uno qualsiasi degli input può essere negativo; secondo il tuo comparatore, 0 ordina prima di 1 che ordina prima di -1 che ordina prima di 0.
Ilmari Karonen

Aggiornato con input negativi
Harunur Rashid

@SalmanA, Se entrambi sono zero, la condizione !aè ancora vera. Tornerà-1
Harunur Rashid il

Non penso che sia un grosso problema sia a che b sono entrambi zero @SalmanA
Harunur Rashid

1
Per l'esattezza, ho modificato la risposta. Ho appena aggiunto una condizione pera=b=0
Harunur Rashid,

6

Non giocare a code golf qui:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, -1];
arr.sort(function(a, b) {
  if (a === 0 && b !== 0) {
    // a is zero b is nonzero, a goes first
    return -1;
  } else if (a !== 0 && b === 0) {
    // a is nonzero b is zero, b goes first
    return 1;
  } else {
    // both are zero or both are nonzero, sort descending
    return b - a;
  }
});
console.log(arr.toString());


5

Non scrivere il tuo ordinamento numerico se esiste già. Quello che vuoi fare è esattamente quello che dici nel titolo; ordina i numeri in ordine decrescente tranne zero all'inizio.

const zeroSort = arr => [...arr.filter(n => n == 0),
                         ...new Float64Array(arr.filter(n => n != 0)).sort().reverse()];

console.log(zeroSort([0, 1, 0, 2, 0, 3, 0, 4, 0, 500]));

Non scrivere alcun codice che non è necessario; potresti sbagliarti.

Scegli l' array Typed in base al tipo di numeri che desideri che l'array gestisca. Float64 è un buon valore predefinito poiché gestisce tutti i normali numeri JS.


@IlmariKaronen Better?
JollyJoker,

Non è necessario filtrare due volte; come mostra la mia risposta, puoi filtrare una volta e sottrarre lunghezze per ottenere un numero Array(n).fill(0).
Peter Cordes,

@PeterCordes Anche se potrebbe essere chiamato più semplice, penso che il codice diventi abbastanza lungo da essere meno facile da leggere
JollyJoker

Sì, l'efficienza richiederebbe 1 riga aggiuntiva per un var tmp. La mia risposta usa più righe extra perché sono abituato al linguaggio C e C ++ e al linguaggio assembly, e avere più righe separate rende più facile rintracciare l'asm generato dal compilatore all'istruzione responsabile per l'ottimizzazione / profilazione. Inoltre, normalmente non faccio JS, quindi non ho sentito il bisogno di stipare tutto su un paio di righe. Ma potresti farlo let f64arr = new Float64Array(arr.filter(n => n != 0)), quindi [ ...Array(arr.length - f64arr.length).fill(0),... quindi aggiunge 1 riga aggiuntiva e semplifica l'ultima riga.
Peter Cordes,

4

Puoi farlo in questo modo:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort((a,b) => {
  if(a == 0 || b == 0)
    return a-b;
  return b-a;
})
console.log(result)

oppure puoi farlo:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort().sort((a,b) => {
  if(a > 0 && b > 0)
    return b-a
  return 0
})

console.log(result)


4
La tua prima soluzione presenta lo stesso problema di coerenza con input negativi della risposta di Harunur Rashid. Il secondo è coerente, ma considera tutti i numeri negativi uguali a zero, lasciando indefinito il loro ordinamento reciproco.
Ilmari Karonen,

-2

const myArray = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];
const splitArray = myArray.reduce((output, item) => {
     if(!item) output[0].push(item);
    else output[1].push(item);
    return output;
}, [[], []]);
const result = [...splitArray[0], ...splitArray[1].reverse()]
console.log(result);

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.