Classificando números em ordem decrescente, mas com `0`s no início


36

Tenho um desafio em JavaScript que já estou tentando descobrir há um tempo.

Considere esta matriz:

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

Eu tenho que produzir este resultado:

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

Estou seguindo esta linha de lógica para posicionar os zeros na frente, ajustando o valor do índice:

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

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

    return y - x;
});

Mas estou preso neste resultado:

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

Alguém tem alguma dica sobre como resolver isso?


6
É garantido que não há números negativos?
Michael - Onde está Clay Shirky,

2
Não é corrigido se a última linha for alterada para return x - y;?
achou do

2
Seria eficiente no JavaScript contar e remover zeros, classificar os elementos restantes normalmente? (sem um comparador personalizado, espero que o mecanismo JS possa usar uma função interna de classificação de números). Anexe o número certo de zeros ao resultado. Se houver muitos zeros, removê-los antes da classificação cria um problema menor. Ou troque os zeros para o início da matriz com uma passagem e classifique a cauda da matriz?
Peter Cordes

2
Isso chega a acontecer return y - x;? Mesmo em javascript, não consigo pensar em nada que não fosse nem ===0nem !==0.
George T

Respostas:


35

Você pode classificar pelo delta de be a(para classificação decrescente) e pegar Number.MAX_VALUE, para valores falsos como zero.

Este:

Number.MAX_VALUE - Number.MAX_VALUE

é igual 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
Sua função de comparação retornará NaNse ambos ae bforem zero. Esse pode ser um comportamento indesejado.
dan04

20
Os resultados de Array.prototype.sortsão definidos pela implementação se o comparador retornar NaN, portanto, esse comparador é uma má ideia. Ele tenta ser inteligente e erra.
user2357112 suporta Monica

3
E se um elemento dentro da matriz for Infinito? A função de comparação retorna 0 ao comparar um número Infinito a 0.
Marco

4
obrigado a todos. Eu pego o maior número que pode ser subtraído por si só, e espero que esse número não seja usado na matriz do op.
Nina Scholz

4
Absolutamente ilegível. Legal, mas ilegível.
Salman A

24

Como mdn docs diz:

Se aeb são dois elementos comparados, então:

Se compareFunction(a, b)retornar menor que 0, classifique apara um índice menor que b(isto é, a vem primeiro).

Se compareFunction(a, b)retornar 0, deixe aeb inalterados um com o outro, mas classificados com relação a todos os elementos diferentes. Nota: o padrão ECMAscript não garante esse comportamento; portanto, nem todos os navegadores (por exemplo, versões do Mozilla que datam de pelo menos 2003) respeitam isso.

Se compareFunction(a, b) retornar maior que 0, classifique bpara um índice menor que a (isto é, bvem primeiro).

compareFunction(a, b)sempre deve retornar o mesmo valor quando recebe um par específico de elementos ae bcomo seus dois argumentos. Se resultados inconsistentes forem retornados, a ordem de classificação será indefinida.

Portanto, a função de comparação tem o seguinte formato:

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 por fornecer a que parece ser a única solução até agora com uma função de comparação realmente consistente (conforme definido no padrão ) para todas as entradas, incluindo números negativos.
Ilmari Karonen 20/11/19

3
@IlmariKaronen: Infelizmente, a comparação é consistente para números negativos, mas é a comparação errada para números negativos. Os negativos são classificados antes de 0 com esse comparador e são classificados em ordem crescente.
User2357112 suporta Monica

2
@ user2357112supportsMonica No entanto, o OP não especificou o comportamento desejado para números negativos e, com este protótipo, é trivial ajustar o comportamento de números negativos para atender a qualquer requisito que o OP tenha. O que há de bom nessa resposta é que ela é idiomática e usa uma função de comparação padrão para fazer o trabalho.
J ...

13

Se você se preocupa com eficiência, provavelmente é mais rápido filtrar os zeros primeiro . Você não quer sortperder tempo olhando para eles, muito menos adicionando trabalho extra ao seu retorno de chamada de comparação para lidar com esse caso especial.

Especialmente se você espera um número significativo de zeros, uma passagem sobre os dados para filtrá-los deve ser muito melhor do que fazer uma classificação O (N log N) maior que analisará cada zero várias vezes.

Você pode acrescentar com eficiência o número certo de zeros depois de terminar.

Também é fácil ler o código resultante. Usei o TypedArray porque é eficiente e facilita a classificação numérica . Mas você pode usar essa técnica com Array regular, usando o idioma padrão 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);
*/

Não sei se TypedArray .sort()e, em seguida, .reverseé mais rápido do que usar uma função de comparação personalizada para classificar em ordem decrescente. Ou se pudermos copiar e reverter em tempo real com um iterador.


Também vale a pena considerar: use apenas um TypedArray de todo o comprimento .

Em vez de usar .filter, faça um loop sobre ele e troque os zeros para a frente da matriz à medida que avança. Isso leva uma passagem sobre seus dados.

Em seguida, use .subarray()para obter uma nova exibição TypedArray dos elementos diferentes de zero do mesmo ArrayBuffer subjacente. Classificação que deixará a matriz completa com um início zero e uma cauda classificada, com a classificação apenas olhando para os elementos diferentes de zero.

Não vi uma função de partição nos métodos Array ou TypedArray, mas mal conheço JavaScript. Com um bom JIT, um loop não deve ser muito pior do que um método interno. (Especialmente quando esse método envolve um retorno de chamada .filtere, a menos que ele use reallocsob o capô para diminuir, ele precisa descobrir quanta memória alocar antes de realmente filtrar).

Eu usei Array regular .filter() antes de converter para um TypedArray. Se sua entrada já é um TypedArray, você não tem esse problema, e essa estratégia se torna ainda mais atraente.


Provavelmente, isso pode ser mais simples e / ou mais idiomático, ou pelo menos mais compacto. IDK se os mecanismos JS conseguirem eliminar / otimizar grande parte da cópia ou inicializar com zero, ou se isso ajudaria a usar loops em vez dos métodos TypedArray, por exemplo, copiar para trás em vez de reverse + .set. Não cheguei a testá-lo rapidamente; se alguém quiser fazer isso, eu estaria interessado.
Peter Cordes

De acordo com o teste de @ Salman, essa resposta funciona como uma porcaria para matrizes pequenas (de ~ 1000 elementos, 10% dos quais são 0). Presumivelmente, toda a criação de novas matrizes dói. Ou talvez descuidada mistura de TypedArray e regular? A partição no local dentro de um TypedArray em uma classificação de subarray zero e não zero + pode ser muito melhor, conforme sugerido na segunda seção da minha resposta.
Peter Cordes

8

Apenas modifique a condição da sua função de comparação assim:

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
Este não é um comparador consistente se alguma das entradas puder ser negativa; de acordo com o seu comparador, 0 tipo antes que classifica uma -1 antes que os tipos antes 0.
Ilmari Karonen

Atualizado com entradas negativas
Harunur Rashid

@ SalmanA, Se ambos forem zero, a condição !aainda é verdadeira. Ele retornará-1
Harunur Rashid 21/11/19

Eu não acho que isso é um grande negócio é um e b ambos são zero @SalmanA
Harunur Rashid

11
Para ser exato, editei a resposta. Acabei de adicionar uma condição paraa=b=0
Harunur Rashid

6

Não está a jogar golfe com códigos aqui:

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

Não escreva sua própria classificação numérica, se ela já existir. O que você quer fazer é exatamente o que você diz no título; classifique os números em ordem decrescente, exceto os zeros no início.

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ão escreva nenhum código que não precise; você pode entender errado.

Escolha o TypedArray com base no tipo de número que você deseja que a matriz manipule. Float64 é um bom padrão, pois lida com todos os números JS normais.


@IlmariKaronen Melhor?
JollyJoker #

Você não precisa filtrar duas vezes; como minha resposta mostra, você pode filtrar uma vez e subtrair comprimentos para obter um número Array(n).fill(0).
Peter Cordes

@PeterCordes Embora ele poderia ser chamado mais simples, acho que o código obtém o tempo suficiente para ser menos fácil de ler
jollyjoker

Sim, a eficiência exigiria uma linha extra para uma variável tmp. Minha resposta usa várias linhas extras porque estou acostumada a C e C ++ e linguagem assembly, e ter mais linhas separadas facilita o rastreamento do asm gerado pelo compilador de volta à declaração responsável por ele ao otimizar / criar perfis. Além disso, eu normalmente não faço JS, então não senti necessidade de colocar tudo em algumas linhas. Mas você poderia fazer let f64arr = new Float64Array(arr.filter(n => n != 0)), então [ ...Array(arr.length - f64arr.length).fill(0),... então adiciona 1 linha extra e simplifica a última linha.
Peter Cordes

4

Você pode fazer isso assim:

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 você pode fazer isso:

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
Sua primeira solução tem o mesmo problema de consistência com dados negativos que a resposta de Harunur Rashid. O segundo é consistente, mas trata todos os números negativos como iguais a zero, deixando sua ordem de classificação mútua indefinida.
Ilmari Karonen 20/11/19

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.