Ordenar números en orden descendente pero con `0`s al inicio

36

Tengo un desafío en JavaScript que ya estoy tratando de resolver por un tiempo.

Considere esta matriz:

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

Tengo que dar salida a este resultado:

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

Estoy siguiendo esta línea de lógica para colocar los ceros al frente, ajustando el valor del índice:

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

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

    return y - x;
});

Pero estoy atrapado en este resultado:

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

¿Alguien tiene algún consejo sobre cómo resolver esto?

lianbwl
fuente
66
¿Está garantizado que no hay números negativos?
Michael - ¿Dónde está Clay Shirky?
2
¿No se soluciona si se cambia la última línea return x - y;?
Mooing Duck
2
¿Sería eficiente en JavaScript contar y eliminar ceros, ordenar los elementos restantes normalmente? (sin un comparador personalizado, así que espero que el motor JS pueda usar una función de clasificación de números incorporada). Luego anteponga el número correcto de ceros al resultado. Si hay muchos ceros, eliminarlos antes de ordenarlos hace un problema menor. ¿O intercambie los ceros al comienzo de la matriz con una pasada, luego ordene la cola de la matriz?
Peter Cordes el
2
¿Esto alguna vez llega a return y - x;? Incluso en javascript, no puedo pensar en nada que no sea ni ===0ni !==0.
George T
2
JSPerf para respuestas: jsperf.com/stackoverflow-question-58933996-v2
Salman A

Respuestas:

35

Puede ordenar por el delta de by a(para la ordenación descendente) y tomar Number.MAX_VALUE, para valores falsos como cero.

Esta:

Number.MAX_VALUE - Number.MAX_VALUE

es igual a cero

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
fuente
13
Su función de comparación devolverá NaNsi ambos ay bson cero. Esto puede ser un comportamiento no deseado.
dan04
20
Los resultados de Array.prototype.sortestán definidos por la implementación si el comparador alguna vez regresa NaN, por lo que este comparador es una mala idea. Intenta ser inteligente y se equivoca.
user2357112 es compatible con Monica el
3
¿Qué pasa si un elemento dentro de la matriz es Infinito? La función de comparación devuelve 0 cuando se compara un número de Infinity con 0.
Marco
44
gracias a todos. Tomo el mayor número que se puede restar por sí mismo, y espero que este número no se use en la matriz de operaciones.
Nina Scholz el
44
Absolutamente no legible. Genial, pero ilegible.
Salman A
24

Como dice mdn docs:

Si ayb son dos elementos que se comparan, entonces:

Si compareFunction(a, b)devuelve menos de 0, ordene aa un índice menor que b(es decir, a viene primero).

Si compareFunction(a, b)devuelve 0, deje a y b sin cambios entre sí, pero ordenados con respecto a todos los elementos diferentes. Nota: el estándar ECMAscript no garantiza este comportamiento, por lo tanto, no todos los navegadores (por ejemplo, las versiones de Mozilla que datan de al menos 2003) lo respetan.

Si compareFunction(a, b) devuelve mayor que 0, ordene ba un índice menor que a (es decir, lo bprimero).

compareFunction(a, b)siempre debe devolver el mismo valor cuando se le da un par específico de elementos ay bcomo sus dos argumentos. Si se devuelven resultados inconsistentes, el orden de clasificación no está definido.

Entonces, la función de comparación tiene la siguiente 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);

Aumentar
fuente
3
+1 para dar lo que parece ser la única solución hasta ahora con una función de comparación que es realmente consistente (como se define en el estándar ) para todas las entradas, incluidos los números negativos.
Ilmari Karonen
3
@IlmariKaronen: Desafortunadamente, la comparación es consistente para números negativos, pero es la comparación incorrecta para números negativos. Los negativos se ordenan antes de 0 con este comparador, y se ordenan en orden ascendente.
user2357112 es compatible con Monica el
2
@ user2357112supportsMonica Sin embargo, OP no ha especificado el comportamiento deseado para los números negativos y, con este prototipo, es trivial ajustar el comportamiento de los números negativos para que se ajuste a cualquier requisito que OP tenga. Lo bueno de esta respuesta es que es idiomática y utiliza una función de comparación estándar para hacer el trabajo.
J ...
13

Si le importa la eficiencia, probablemente sea más rápido filtrar los ceros primero . No quiere sortperder el tiempo ni siquiera mirándolos, y mucho menos agregando trabajo adicional a su devolución de llamada de comparación para manejar ese caso especial.

Especialmente si espera un número significativo de ceros, una pasada sobre los datos para filtrarlos debería ser mucho mejor que hacer una clasificación O (N log N) más grande que analizará cada cero varias veces.

Puede anteponer eficientemente el número correcto de ceros una vez que haya terminado.

También es igual de fácil leer el código resultante. Utilicé TypedArray porque es eficiente y facilita la clasificación numérica . Pero puede usar esta técnica con Array regular, usando el idioma estándar 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);
*/

No sé si TypedArray .sort()y luego .reversees más rápido que usar una función de comparación personalizada para ordenar en orden descendente. O si podemos copiar y revertir sobre la marcha con un iterador.


También vale la pena considerarlo: solo use un TypedArray de longitud completa .

En lugar de usar .filter, repítelo y cambia los ceros al frente de la matriz a medida que avanzas. Esto toma una pasada sobre sus datos.

Luego, use .subarray()para obtener una nueva vista TypedArray de los elementos distintos de cero del mismo ArrayBuffer subyacente. Clasificación que le dejará la matriz completa con un inicio cero y una cola ordenada, con la clasificación solo mirando los elementos que no son cero.

No vi una función de partición en los métodos Array o TypedArray, pero apenas conozco JavaScript. Con un buen JIT, un bucle no debería ser mucho peor que un método incorporado. (Especialmente cuando ese método implica una devolución de llamada como .filter, y a menos que se utilice reallocdebajo del capó para reducir, tiene que averiguar cuánta memoria asignar antes de que realmente se filtre).

Usé Array regular .filter() antes de convertir a un TypedArray. Si su entrada ya es un TypedArray, no tiene este problema, y ​​esta estrategia se vuelve aún más atractiva.

Peter Cordes
fuente
Esto probablemente puede ser más simple y / o más idiomático, o al menos más compacto. IDK si los motores JS logran eliminar / optimizar gran parte de la copia o inicialización cero, o si ayudaría a usar bucles en lugar de los métodos TypedArray, por ejemplo, para copiar hacia atrás en lugar de invertir + .set. No llegué a probarlo a velocidad; si alguien quiere hacer eso me interesaría.
Peter Cordes el
De acuerdo con las pruebas de @ Salman, esta respuesta funciona como basura para matrices más pequeñas (de ~ 1000 elementos, 10% de los cuales son 0). Presumiblemente, toda la creación de nuevas matrices duele. ¿O tal vez una mezcla descuidada de TypedArray y regular? La partición in situ dentro de un TypedArray en ordenación de subarreglos todo cero y no cero + podría ser mucho mejor, como se sugiere en la segunda sección de mi respuesta.
Peter Cordes el
8

Simplemente modifique la condición de su función de comparación de esta manera:

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
fuente
66
Este no es un comparador consistente si alguna de las entradas puede ser negativa; según su comparador, 0 ordena antes que 1 que ordena antes que -1 que ordena antes 0.
Ilmari Karonen
Actualizado con entradas negativas
Harunur Rashid
@SalmanA, si ambos son cero, la condición !asigue siendo cierta. Volverá-1
Harunur Rashid
No creo que sea un gran problema ayb ambos son cero @SalmanA
Harunur Rashid
1
Para ser exactos, edité la respuesta. Acabo de agregar una condición paraa=b=0
Harunur Rashid
6

No estoy jugando código de golf aquí:

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
fuente
5

No escriba su propia ordenación numérica si ya existe. Lo que quieres hacer es exactamente lo que dices en el título; ordenar los números en orden descendente, excepto los ceros al comienzo.

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

No escriba ningún código que no necesite; podrías equivocarte.

Elija el TypedArray según el tipo de números que desea que maneje el arreglo . Float64 es un buen valor predeterminado ya que maneja todos los números JS normales.

Bromista alegre
fuente
@IlmariKaronen ¿Mejor?
JollyJoker
No necesita filtrar dos veces; como muestra mi respuesta, puedes filtrar una vez y restar longitudes para obtener un número Array(n).fill(0).
Peter Cordes el
@PeterCordes Aunque posiblemente podría llamarse más simple, creo que el código es lo suficientemente largo como para ser menos fácil de leer
JollyJoker
Sí, la eficiencia requeriría una línea adicional 1 para una tmp var. Mi respuesta usa varias líneas adicionales porque estoy acostumbrado a C y C ++ y al lenguaje ensamblador, y tener más líneas separadas hace que sea más fácil rastrear el asm generado por el compilador hasta la declaración responsable de ello al optimizar / perfilar. Además, normalmente no hago JS, así que no sentí la necesidad de incluir todo en un par de líneas. Pero podría hacerlo let f64arr = new Float64Array(arr.filter(n => n != 0)), entonces [ ...Array(arr.length - f64arr.length).fill(0),... entonces agrega 1 línea extra y simplifica la última línea.
Peter Cordes el
4

Puedes hacer esto así:

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)

o puedes hacer esto:

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
fuente
44
Su primera solución tiene el mismo problema de coherencia con las entradas negativas que la respuesta de Harunur Rashid. El segundo es consistente, pero trata todos los números negativos como iguales a cero, dejando su orden de clasificación mutuo indefinido.
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
fuente