Sortieren von Zahlen in absteigender Reihenfolge, jedoch mit "0" am Anfang

36

Ich habe eine Herausforderung in JavaScript, die ich bereits seit einiger Zeit herausfinden möchte.

Betrachten Sie dieses Array:

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

Ich muss dieses Ergebnis ausgeben:

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

Ich folge dieser Logik, um die Nullen vorne zu positionieren und den Indexwert anzupassen:

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

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

    return y - x;
});

Aber ich stecke bei diesem Ergebnis fest:

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

Hat jemand irgendwelche Tipps, wie man das löst?

lianbwl
quelle
6
Ist garantiert, dass es keine negativen Zahlen gibt?
Michael - Wo ist Clay Shirky
2
Behebt es nicht, wenn die letzte Zeile auf geschaltet wird return x - y;?
Mooing Duck
2
Wäre es in JavaScript effizient, Nullen zu zählen und zu entfernen und die verbleibenden Elemente normal zu sortieren? (Ohne einen benutzerdefinierten Komparator kann die JS-Engine hoffentlich eine integrierte Funktion zum Sortieren von Zahlen verwenden.) Stellen Sie dann dem Ergebnis die richtige Anzahl von Nullen voran. Wenn es viele Nullen gibt, ist das Entfernen dieser Nullen vor dem Sortieren ein kleineres Problem. Oder tauschen Sie die Nullen mit einem Durchgang an den Anfang des Arrays und sortieren Sie dann das Ende des Arrays?
Peter Cordes
2
Kommt das jemals dazu return y - x;? Selbst in Javascript fällt mir nichts ein, was weder ===0noch wäre !==0.
George T
2
JSPerf für Antworten: jsperf.com/stackoverflow-question-58933996-v2
Salman A

Antworten:

35

Sie können nach dem Delta von bund a(für absteigende Sortierung) sortieren und Number.MAX_VALUEfür falsche Werte wie Null nehmen.

Diese:

Number.MAX_VALUE - Number.MAX_VALUE

ist gleich Null.

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
quelle
13
Ihre Vergleichsfunktion wird zurückgegeben, NaNwenn beide aund bNull sind. Dies kann unerwünschtes Verhalten sein.
Dan04
20
Die Ergebnisse von Array.prototype.sortsind implementierungsdefiniert, falls der Komparator jemals zurückkehrt NaN. Daher ist dieser Komparator eine schlechte Idee. Es versucht klug zu sein und macht es falsch.
user2357112 unterstützt Monica
3
Was ist, wenn ein Element im Array Infinity ist? Die Vergleichsfunktion gibt 0 zurück, wenn eine Unendlichkeitszahl mit 0 verglichen wird.
Marco
4
Danke euch allen. Ich nehme die größte Zahl, die von selbst subtrahiert werden kann, und hoffe, dass diese Zahl nicht im Array von op verwendet wird.
Nina Scholz
4
Absolut unlesbar. Cool, aber unlesbar.
Salman A
24

Wie mdn docs sagt:

Wenn a und b zwei Elemente sind, die verglichen werden, dann:

Wenn compareFunction(a, b)weniger als zurückgegeben wird 0, sortieren Sie anach einem Index, der niedriger als ist b(dh a steht an erster Stelle).

Wenn compareFunction(a, b)0 zurückgegeben wird, lassen Sie a und b unverändert zueinander, aber sortiert nach allen verschiedenen Elementen. Hinweis: Der ECMAscript-Standard garantiert dieses Verhalten nicht. Daher respektieren dies nicht alle Browser (z. B. Mozilla-Versionen aus mindestens 2003).

Wenn eine compareFunction(a, b) Rückgabe größer als 0 ist, sortieren Sie bnach einem Index kleiner als a (dh bsteht an erster Stelle).

compareFunction(a, b)muss immer den gleichen Wert zurückgeben, wenn ein bestimmtes Elementpaar aund bseine beiden Argumente angegeben werden. Wenn inkonsistente Ergebnisse zurückgegeben werden, ist die Sortierreihenfolge undefiniert.

Die Vergleichsfunktion hat also die folgende Form:

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
quelle
3
+1 für die Angabe der bislang einzigen Lösung mit einer Vergleichsfunktion, die für alle Eingaben, einschließlich negativer Zahlen , tatsächlich konsistent ist (wie im Standard definiert ).
Ilmari Karonen
3
@IlmariKaronen: Leider ist der Vergleich für negative Zahlen konsistent , aber es ist der falsche Vergleich für negative Zahlen. Negative sortieren mit diesem Komparator vor 0 und in aufsteigender Reihenfolge.
user2357112 unterstützt Monica
2
@ user2357112supportsMonica Trotzdem hat OP das gewünschte Verhalten für negative Zahlen nicht angegeben, und bei diesem Prototyp ist es trivial, das Verhalten negativer Zahlen an die Anforderungen des OP anzupassen. Das Gute an dieser Antwort ist, dass sie idiomatisch ist und eine Standardvergleichsfunktion verwendet, um die Arbeit zu erledigen.
J ...
13

Wenn Sie Wert auf Effizienz legen, ist es wahrscheinlich am schnellsten, zuerst die Nullen herauszufiltern . Sie möchten keine sortZeit damit verschwenden, sie sich anzusehen, geschweige denn Ihrem Vergleichsrückruf zusätzliche Arbeit hinzuzufügen, um diesen Sonderfall zu behandeln.

Insbesondere wenn Sie eine signifikante Anzahl von Nullen erwarten, sollte ein Durchlauf über die Daten zum Herausfiltern viel besser sein als eine größere O (N log N) -Sortierung, bei der jede Null mehrmals betrachtet wird.

Sie können die richtige Anzahl von Nullen effizient voranstellen, nachdem Sie fertig sind.

Genauso einfach ist es, den resultierenden Code zu lesen. Ich habe TypedArray verwendet, weil es effizient ist und das numerische Sortieren vereinfacht . Sie können diese Technik jedoch mit regulären Arrays verwenden, wobei Sie die Standardsprache (a,b)=>a-bfor verwenden .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);
*/

Ich weiß nicht, ob TypedArray .sort()und dann .reverseschneller als die Verwendung einer benutzerdefinierten Vergleichsfunktion zum Sortieren in absteigender Reihenfolge. Oder wenn wir mit einem Iterator im laufenden Betrieb kopieren und umkehren können.


Ebenfalls erwägenswert: Verwenden Sie nur ein TypedArray in voller Länge .

Anstatt zu verwenden .filter, schleifen Sie darüber und tauschen Sie die Nullen an die Vorderseite des Arrays, während Sie fortfahren. Dies dauert einen Durchgang über Ihre Daten.

Verwenden Sie dann .subarray(), um eine neue TypedArray-Ansicht der Nicht-Null-Elemente desselben zugrunde liegenden ArrayBuffers abzurufen. Durch die Sortierung erhalten Sie das gesamte Array mit einem Nullstart und einem sortierten Ende, wobei die Sortierung immer nur die Nicht-Null-Elemente betrachtet.

Ich habe keine Partitionsfunktion in den Array- oder TypedArray-Methoden gesehen, kenne aber kaum JavaScript. Mit einer guten JIT sollte eine Schleife nicht zu viel schlechter sein als eine integrierte Methode. (Insbesondere wenn diese Methode einen Rückruf wie beinhaltet .filterund nicht reallocunter der Haube zum Verkleinern verwendet wird, muss sie herausfinden, wie viel Speicher zugewiesen werden muss, bevor sie tatsächlich gefiltert wird.)

Ich habe .filter() vor der Konvertierung in ein TypedArray ein reguläres Array verwendet. Wenn Ihre Eingabe bereits ein TypedArray ist, haben Sie dieses Problem nicht und diese Strategie wird noch attraktiver.

Peter Cordes
quelle
Dies kann wahrscheinlich einfacher und / oder idiomatischer oder zumindest kompakter sein. IDK, wenn es JS-Engines gelingt, einen Großteil des Kopierens oder Null-Initialisierens zu eliminieren / zu optimieren, oder wenn es hilfreich wäre, Schleifen anstelle von TypedArray-Methoden zu verwenden, z. B. rückwärts statt rückwärts + .set zu kopieren. Ich bin nicht dazu gekommen, es auf Geschwindigkeit zu testen. Wenn jemand das tun möchte, wäre ich interessiert.
Peter Cordes
Laut @ Salmans Test verhält sich diese Antwort wie Mist für kleinere Arrays (von ~ 1000 Elementen, von denen 10% 0 sind). Vermutlich tut die Erstellung neuer Arrays weh. Oder vielleicht unachtsames Mischen von TypedArray und Regular? Eine direkte Partition innerhalb eines TypedArray in eine Subarray-Sortierung von Null und Nicht-Null + ist möglicherweise viel besser, wie im zweiten Abschnitt meiner Antwort vorgeschlagen.
Peter Cordes
8

Ändern Sie einfach den Zustand Ihrer Vergleichsfunktion wie folgt:

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
quelle
6
Dies ist kein konsistenter Komparator, wenn einer der Eingänge negativ sein kann. Laut Ihrem Komparator sortiert 0 vor 1, die vor -1 sortiert, die vor 0 sortiert.
Ilmari Karonen
Aktualisiert mit negativen Eingaben
Harunur Rashid
@SalmanA, Wenn beide Null sind, ist die Bedingung !aimmer noch wahr. Es wird zurückkehren-1
Harunur Rashid
Ich glaube nicht, dass das eine große Sache ist, a und b sind beide Null @SalmanA
Harunur Rashid
1
Um genau zu sein, habe ich die Antwort bearbeitet. Gerade eine Bedingung füra=b=0
Harunur Rashid
6

Hier kein Code Golf spielen:

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.
quelle
5

Schreiben Sie keine eigene numerische Sortierung, wenn diese bereits vorhanden ist. Was Sie tun möchten, ist genau das, was Sie im Titel sagen. Sortieren Sie die Nummern in absteigender Reihenfolge mit Ausnahme von Nullen am Anfang.

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

Schreiben Sie keinen Code, den Sie nicht benötigen. Sie könnten es falsch verstehen.

Wählen Sie das TypedArray basierend auf dem Typ der Zahlen aus, die das Array verarbeiten soll. Float64 ist eine gute Standardeinstellung, da alle normalen JS-Nummern verarbeitet werden.

JollyJoker
quelle
@IlmariKaronen Besser?
JollyJoker
Sie müssen nicht zweimal filtern. Wie meine Antwort zeigt, können Sie einmal filtern und Längen subtrahieren, um eine Zahl für zu erhalten Array(n).fill(0).
Peter Cordes
@ PeterCordes Obwohl es möglicherweise einfacher genannt werden könnte, denke ich, dass der Code lang genug wird, um weniger leicht zu lesen zu sein
JollyJoker
Ja, für die Effizienz wäre eine zusätzliche Leitung für eine tmp-Variable erforderlich. In meiner Antwort werden mehrere zusätzliche Zeilen verwendet, da ich an C und C ++ sowie die Assemblersprache gewöhnt bin. Wenn Sie mehr separate Zeilen verwenden, können Sie den vom Compiler generierten asm bei der Optimierung / Profilerstellung leichter auf die dafür verantwortliche Anweisung zurückführen. Außerdem mache ich normalerweise kein JS, so dass ich nicht das Bedürfnis hatte, alles auf ein paar Zeilen zu packen. Aber Sie könnten es let f64arr = new Float64Array(arr.filter(n => n != 0))dann tun [ ...Array(arr.length - f64arr.length).fill(0),... also fügt es 1 zusätzliche Zeile hinzu und vereinfacht die letzte Zeile.
Peter Cordes
4

Sie können dies folgendermaßen tun:

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)

oder Sie können dies tun:

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
quelle
4
Ihre erste Lösung hat das gleiche Konsistenzproblem mit negativen Eingaben wie die Antwort von Harunur Rashid. Die zweite ist konsistent, behandelt jedoch alle negativen Zahlen als gleich Null und lässt ihre gegenseitige Sortierreihenfolge undefiniert.
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
quelle