Tri des nombres dans l'ordre décroissant mais avec des «0» au début

36

J'ai un défi en JavaScript que j'essaie de comprendre depuis un moment déjà.

Considérez ce tableau:

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

Je dois sortir ce résultat:

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

Je suis cette ligne de logique pour positionner les zéros devant, en ajustant la valeur d'index:

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

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

    return y - x;
});

Mais je suis bloqué sur ce résultat:

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

Quelqu'un a-t-il des conseils pour résoudre ce problème?

lianbwl
la source
6
Est-il garanti qu'il n'y a pas de nombres négatifs?
Michael - Où est Clay Shirky
2
Cela ne résout-il pas si la dernière ligne est commutée return x - y;?
Mooing Duck
2
Serait-il efficace en JavaScript de compter et de supprimer les zéros, de trier les éléments restants normalement? (sans comparateur personnalisé, nous espérons donc que le moteur JS pourra utiliser une fonction de tri numérique intégrée). Ajoutez ensuite le bon nombre de zéros au résultat. S'il y a beaucoup de zéros, leur suppression avant le tri rend le problème plus petit. Ou permuter les zéros au début du tableau en un seul passage, puis trier la queue du tableau?
Peter Cordes
2
Est-ce que cela arrive même return y - x;? Même en javascript, je ne peux penser à rien qui ne serait ni ===0ni !==0.
George T
2
JSPerf pour les réponses: jsperf.com/stackoverflow-question-58933996-v2
Salman A

Réponses:

35

Vous pouvez trier par le delta de bet a(pour le tri décroissant) et prendre Number.MAX_VALUE, pour des valeurs fausses comme zéro.

Cette:

Number.MAX_VALUE - Number.MAX_VALUE

est égal à zéro.

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

Nina Scholz
la source
13
Votre fonction de comparaison retournera NaNsi les deux aet bsont nuls. Cela peut être un comportement indésirable.
dan04
20
Les résultats de Array.prototype.sortsont définis par l'implémentation si le comparateur revient jamais NaN, donc ce comparateur est une mauvaise idée. Il essaie d'être intelligent et se trompe.
user2357112 prend en charge Monica
3
Et si un élément à l'intérieur du tableau est Infinity? La fonction de comparaison renvoie 0 lors de la comparaison d'un nombre Infinity à 0.
Marco
4
Merci à tous. je prends le plus grand nombre qui peut être soustrait par lui-même, et j'espère que ce nombre n'est pas utilisé dans le tableau d'op.
Nina Scholz
4
Absolument illisible. Cool, mais illisible.
Salman A
24

Comme le dit mdn docs:

Si a et b sont deux éléments comparés, alors:

Si compareFunction(a, b)renvoie moins de 0, triez aselon un indice inférieur à b(c.-à-d. A vient en premier).

Si compareFunction(a, b)renvoie 0, laissez a et b inchangés l'un par rapport à l'autre, mais triés par rapport à tous les différents éléments. Remarque: la norme ECMAscript ne garantit pas ce comportement, donc tous les navigateurs (par exemple les versions de Mozilla datant d'au moins 2003) respectent cela.

Si compareFunction(a, b) renvoie plus de 0, triez bsur un index inférieur à a (c'est-à-dire bvient en premier).

compareFunction(a, b)doit toujours renvoyer la même valeur lorsque donné une paire spécifique d'éléments aet bcomme ses deux arguments. Si des résultats incohérents sont renvoyés, l'ordre de tri n'est pas défini.

Ainsi, la fonction de comparaison a la forme suivante:

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

StepUp
la source
3
+1 pour avoir donné ce qui semble être la seule solution jusqu'à présent avec une fonction de comparaison qui soit réellement cohérente (telle que définie dans la norme ) pour toutes les entrées, y compris les nombres négatifs.
Ilmari Karonen
3
@IlmariKaronen: Malheureusement, la comparaison est cohérente pour les nombres négatifs, mais c'est la mauvaise comparaison pour les nombres négatifs. Les négatifs sont triés avant 0 avec ce comparateur et triés par ordre croissant.
user2357112 prend en charge Monica
2
@ user2357112supportsMonica Néanmoins, OP n'a pas spécifié le comportement souhaité pour les nombres négatifs et, avec ce prototype, il est trivial d'ajuster le comportement des nombres négatifs pour s'adapter aux exigences de OP. Ce qui est bien avec cette réponse, c'est qu'elle est idiomatique et utilise une fonction de comparaison standard pour faire le travail.
J ...
13

Si vous vous souciez de l'efficacité, il est probablement plus rapide de filtrer les zéros en premier . Vous ne voulez sortpas perdre de temps, même en les regardant, sans parler d'ajouter du travail supplémentaire à votre rappel de comparaison pour gérer ce cas spécial.

Surtout si vous vous attendez à un nombre important de zéros, un passage sur les données pour les filtrer devrait être bien mieux que de faire un tri O (N log N) plus grand qui examinera chaque zéro plusieurs fois.

Vous pouvez ajouter efficacement le bon nombre de zéros après avoir terminé.

Il est également tout aussi facile de lire le code résultant. J'ai utilisé TypedArray car il est efficace et facilite le tri numérique . Mais vous pouvez utiliser cette technique avec un tableau standard, en utilisant l'idiome standard de (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);
*/

Je ne sais pas si TypedArray .sort()puis .reverseest plus rapide que d'utiliser une fonction de comparaison personnalisée pour trier par ordre décroissant. Ou si nous pouvons copier et inverser à la volée avec un itérateur.


À considérer également: n'utilisez qu'un seul TypedArray de toute la longueur .

Au lieu de l'utiliser .filter, faites une boucle dessus et permutez les zéros à l'avant du tableau au fur et à mesure. Cela prend un seul passage sur vos données.

Ensuite, utilisez .subarray()pour obtenir une nouvelle vue TypedArray des éléments non nuls du même ArrayBuffer sous-jacent. Un tri qui vous laissera le tableau complet avec un début zéro et une queue triée, le tri ne regardant que les éléments non nuls.

Je n'ai pas vu de fonction de partition dans les méthodes Array ou TypedArray, mais je connais à peine JavaScript. Avec un bon JIT, une boucle ne devrait pas être bien pire qu'une méthode intégrée. (Surtout lorsque cette méthode implique un rappel .filter, et à moins qu'elle n'utilise reallocsous le capot pour rétrécir, elle doit déterminer la quantité de mémoire à allouer avant qu'elle ne filtre réellement).

J'ai utilisé regular-Array .filter() avant de convertir en TypedArray. Si votre entrée est déjà un TypedArray, vous n'avez pas ce problème et cette stratégie devient encore plus attrayante.

Peter Cordes
la source
Cela peut probablement être plus simple et / ou plus idiomatique, ou du moins plus compact. IDK si les moteurs JS parviennent à éliminer / optimiser une grande partie de la copie ou de l'initialisation à zéro, ou si cela aiderait à utiliser des boucles au lieu des méthodes TypedArray, par exemple pour copier en arrière au lieu de reverse + .set. Je n'ai pas réussi à le tester rapidement; si quelqu'un veut le faire, je serais intéressé.
Peter Cordes
Selon les tests de @ Salman, cette réponse fonctionne comme de la merde pour les petits tableaux (de ~ 1000 éléments, dont 10% à 0). Vraisemblablement, toute la création de nouveaux tableaux fait mal. Ou peut-être un mélange imprudent de TypedArray et régulier? La partition en place à l'intérieur d'un TypedArray dans un tri de sous-tableau tout à zéro et non nul + pourrait être beaucoup mieux, comme suggéré dans la 2ème section de ma réponse.
Peter Cordes
8

Modifiez simplement l'état de votre fonction de comparaison comme ceci -

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

Harunur Rashid
la source
6
Ce n'est pas un comparateur cohérent si l'une des entrées peut être négative; selon votre comparateur, 0 trie avant 1 qui trie avant -1 qui trie avant 0.
Ilmari Karonen
Mis à jour avec des entrées négatives
Harunur Rashid
@SalmanA, Si les deux sont nuls, la condition !aest toujours vraie. Il reviendra-1
Harunur Rashid
Je ne pense pas que ce soit un gros problème est a et b les deux sont nuls @SalmanA
Harunur Rashid
1
Pour être exact, j'ai édité la réponse. Ajout d'une condition poura=b=0
Harunur Rashid
6

Ne joue pas au golf de code ici:

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());

Salman A
la source
5

N'écrivez pas votre propre tri numérique s'il existe déjà. Ce que vous voulez faire, c'est exactement ce que vous dites dans le titre; trier les nombres dans l'ordre décroissant sauf les zéros au début.

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

N'écrivez aucun code dont vous n'avez pas besoin; vous pourriez vous tromper.

Choisissez le tableau typé en fonction du type de nombres que vous souhaitez que le tableau gère. Float64 est une bonne valeur par défaut car il gère tous les nombres JS normaux.

JollyJoker
la source
@IlmariKaronen Mieux?
JollyJoker
Vous n'avez pas besoin de filtrer deux fois; comme ma réponse le montre, vous pouvez filtrer une fois et soustraire des longueurs pour obtenir un nombre Array(n).fill(0).
Peter Cordes
@PeterCordes Bien que cela puisse être appelé plus simple, je pense que le code devient assez long pour être moins facile à lire
JollyJoker
Oui, l'efficacité nécessiterait une ligne supplémentaire pour une var tmp. Ma réponse utilise plusieurs lignes supplémentaires parce que je suis habitué au C et C ++ et au langage d'assemblage, et avoir des lignes plus séparées facilite le suivi de l'asm généré par le compilateur jusqu'à la déclaration qui en est responsable lors de l'optimisation / profilage. De plus, je ne fais pas normalement de JS, donc je n'ai pas ressenti le besoin de tout entasser sur quelques lignes. Mais vous pourriez le faire let f64arr = new Float64Array(arr.filter(n => n != 0)), alors [ ...Array(arr.length - f64arr.length).fill(0),... alors cela ajoute 1 ligne supplémentaire et simplifie la dernière ligne.
Peter Cordes
4

Vous pouvez faire ceci comme ceci:

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)

ou vous pouvez le faire:

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)

NuOne
la source
4
Votre première solution a le même problème de cohérence avec des entrées négatives que la réponse de Harunur Rashid. Le second est cohérent, mais traite tous les nombres négatifs comme égaux à zéro, laissant leur ordre de tri mutuel non défini.
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);

Max
la source