Mengurutkan angka dalam urutan menurun tetapi dengan `0` di awal

36

Saya memiliki tantangan dalam JavaScript yang sedang saya coba cari tahu.

Pertimbangkan array ini:

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

Saya harus menampilkan hasil ini:

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

Saya mengikuti garis logika ini untuk memposisikan nol di depan, menyesuaikan nilai indeks:

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

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

    return y - x;
});

Tapi saya terjebak pada hasil ini:

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

Adakah yang punya tips bagaimana mengatasi ini?

lianbwl
sumber
6
Apakah dijamin tidak ada angka negatif?
Michael - Di mana Clay Shirky
2
Apakah tidak diperbaiki jika baris terakhir dialihkan return x - y;?
Mooing Duck
2
Apakah efisien dalam JavaScript untuk menghitung dan menghapus nol, mengurutkan elemen yang tersisa secara normal? (tanpa pembanding khusus jadi semoga mesin JS dapat menggunakan fungsi sortir angka bawaan). Kemudian tambahkan jumlah nol yang tepat ke hasilnya. Jika ada banyak nol, menghapusnya sebelum menyortir membuat masalah lebih kecil. Atau menukar nol ke awal array dengan satu lintasan, lalu urutkan ekor array?
Peter Cordes
2
Apakah ini pernah terjadi return y - x;? Bahkan di javascript, saya tidak bisa memikirkan apa pun yang akan baik ===0atau !==0.
George T
2
JSPerf untuk jawaban: jsperf.com/stackoverflow-question-58933996-v2
Salman A

Jawaban:

35

Anda dapat mengurutkan berdasarkan delta bdan a(untuk penyortiran menurun) dan mengambil Number.MAX_VALUE, untuk nilai falsy seperti nol.

Ini:

Number.MAX_VALUE - Number.MAX_VALUE

sama dengan nol.

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
sumber
13
Fungsi perbandingan Anda akan kembali NaNjika keduanya adan bnol. Ini mungkin perilaku yang tidak diinginkan.
dan04
20
Hasil Array.prototype.sortimplementasi ditentukan jika komparator kembali NaN, jadi komparator ini adalah ide yang buruk. Itu mencoba untuk menjadi pintar, dan salah.
user2357112 mendukung Monica
3
Bagaimana jika elemen di dalam array adalah Infinity? Fungsi perbandingan mengembalikan 0 ketika membandingkan angka Infinity ke 0.
Marco
4
Terima kasih semua. saya mengambil angka terbesar yang dapat dikurangi dengan sendirinya, dan berharap nomor ini tidak digunakan dalam array op.
Nina Scholz
4
Sama sekali tidak bisa dibaca. Keren, tapi tidak bisa dibaca.
Salman A
24

Seperti yang dikatakan mdn docs:

Jika a dan b adalah dua elemen yang dibandingkan, maka:

Jika compareFunction(a, b)mengembalikan kurang dari 0, sortir ake indeks yang lebih rendah dari b(mis. Yang lebih dulu).

Jika compareFunction(a, b)mengembalikan 0, biarkan a dan b tidak berubah sehubungan satu sama lain, tetapi diurutkan sehubungan dengan semua elemen yang berbeda. Catatan: standar skrip ECMA tidak menjamin perilaku ini, oleh karena itu, tidak semua browser (misalnya, versi Mozilla yang berasal dari setidaknya tahun 2003) menghargai hal ini.

Jika compareFunction(a, b) mengembalikan lebih besar dari 0, urutkan bke indeks lebih rendah dari a (yaitu yang blebih dulu).

compareFunction(a, b)harus selalu mengembalikan nilai yang sama ketika diberi pasangan elemen tertentu adan bsebagai dua argumennya. Jika hasil yang tidak konsisten dikembalikan, maka urutan sortir tidak ditentukan.

Jadi, fungsi bandingkan memiliki bentuk berikut:

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
sumber
3
+1 untuk memberikan apa yang tampaknya menjadi satu-satunya solusi sejauh ini dengan fungsi perbandingan yang sebenarnya konsisten (sebagaimana didefinisikan dalam standar ) untuk semua input, termasuk angka negatif.
Ilmari Karonen
3
@IlmariKaronen: Sayangnya, perbandingannya konsisten untuk angka negatif, tapi itu perbandingan yang salah untuk angka negatif. Negatif mengurutkan sebelum 0 dengan pembanding ini, dan mereka mengurutkan dalam urutan menaik.
user2357112 mendukung Monica
2
@ user2357112supportsMonica Namun demikian, OP belum menentukan perilaku yang diinginkan untuk angka negatif dan, dengan prototipe ini, sepele untuk menyesuaikan perilaku angka negatif agar sesuai dengan persyaratan apa pun yang dimiliki OP. Apa yang baik tentang jawaban ini adalah idiom dan menggunakan fungsi perbandingan standar untuk melakukan pekerjaan itu.
J ...
13

Jika Anda peduli efisiensi, mungkin yang tercepat adalah menyaring nol terlebih dahulu . Anda tidak ingin sortmembuang waktu bahkan memandangnya, apalagi menambahkan kerja ekstra untuk callback perbandingan Anda untuk menangani kasus khusus itu.

Terutama jika Anda mengharapkan angka nol yang signifikan, satu kali melewati data untuk memfilternya harus jauh lebih baik daripada melakukan jenis O (N log N) yang lebih besar yang akan melihat setiap nol beberapa kali.

Anda dapat dengan efisien menambahkan jumlah nol yang tepat setelah selesai.

Juga mudah untuk membaca kode yang dihasilkan. Saya menggunakan TypedArray karena efisien dan memudahkan pengurutan angka . Tetapi Anda dapat menggunakan teknik ini dengan Array biasa, menggunakan idiom standar (a,b)=>a-buntuk .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);
*/

Saya tidak tahu apakah TypedArray .sort()dan kemudian .reverselebih cepat daripada menggunakan fungsi perbandingan kustom untuk menyortir dalam urutan. Atau jika kita dapat menyalin dan membalikkan dengan cepat menggunakan iterator.


Juga patut dipertimbangkan: hanya gunakan satu TypedArray dengan panjang penuh .

Alih-alih menggunakan .filter, loop di atasnya dan menukar nol ke depan array saat Anda pergi. Ini membutuhkan satu lewati data Anda.

Kemudian gunakan .subarray()untuk mendapatkan tampilan TypedArray baru dari elemen non-nol dari ArrayBuffer yang mendasari yang sama. Pengurutan yang akan membuat Anda array penuh dengan awal nol dan ekor diurutkan, dengan pengurutan hanya pernah melihat elemen non-nol.

Saya tidak melihat fungsi partisi dalam metode Array atau TypedArray, tapi saya hampir tidak tahu JavaScript. Dengan JIT yang baik, sebuah loop seharusnya tidak jauh lebih buruk daripada metode bawaan. (Terutama ketika metode itu melibatkan panggilan balik seperti .filter, dan kecuali jika menggunakan di reallocbawah tenda untuk menyusut, ia harus mencari tahu berapa banyak memori yang dialokasikan sebelum benar-benar menyaring).

Saya menggunakan Array biasa .filter() sebelum mengonversi ke TypedArray. Jika input Anda sudah menjadi TypedArray Anda tidak memiliki masalah ini, dan strategi ini menjadi lebih menarik.

Peter Cordes
sumber
Ini mungkin bisa lebih sederhana, dan / atau lebih idiomatis, atau setidaknya lebih kompak. IDK jika mesin JS berhasil menghilangkan / mengoptimalkan banyak penyalinan atau nol-inisialisasi, atau jika itu akan membantu untuk menggunakan loop daripada metode TypedArray misalnya untuk menyalin ke belakang, bukan sebaliknya + .set. Saya tidak sempat menguji kecepatannya; jika seseorang ingin melakukan itu, saya akan tertarik.
Peter Cordes
Menurut pengujian @ Salman, jawaban ini berfungsi seperti omong kosong untuk array bertubuh kecil (dari ~ 1000 elemen, 10% di antaranya adalah 0). Agaknya semua ciptaan array baru terasa sakit. Atau mungkin pencampuran yang ceroboh dari TypedArray dan reguler? Partisi di tempat di dalam TypedArray ke dalam all-zero dan non-zero + subarray sort mungkin jauh lebih baik, seperti yang disarankan di bagian ke-2 dari jawaban saya.
Peter Cordes
8

Cukup modifikasi kondisi fungsi perbandingan Anda seperti ini -

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
sumber
6
Ini bukan komparator yang konsisten jika salah satu input bisa negatif; menurut pembanding Anda, 0 macam sebelum 1 yang macam sebelum -1 yang macam sebelum 0.
Ilmari Karonen
Diperbarui dengan input negatif
Harunur Rashid
@SalmanA, Jika keduanya nol, kondisinya !amasih benar. Ia akan kembali-1
Harunur Rashid
Saya tidak berpikir itu masalah besar adalah a dan b keduanya nol @SalmanA
Harunur Rashid
1
Tepatnya, saya mengedit jawabannya. Baru saja menambahkan kondisi untuka=b=0
Harunur Rashid
6

Tidak bermain golf kode di sini:

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

Jangan menulis pengurutan angka Anda sendiri jika sudah ada. Apa yang ingin Anda lakukan adalah persis apa yang Anda katakan dalam judul; mengurutkan angka dalam urutan menurun kecuali nol di awal.

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

Jangan menulis kode apa pun yang Anda tidak perlu; Anda mungkin salah.

Pilih TypedArray berdasarkan pada Tipe nomor apa yang Anda inginkan untuk ditangani oleh Array. Float64 adalah default yang baik karena menangani semua nomor JS normal.

JollyJoker
sumber
@IlmariKaronen Lebih Baik?
JollyJoker
Anda tidak perlu memfilter dua kali; seperti jawaban saya menunjukkan Anda dapat memfilter satu kali dan mengurangi panjang untuk mendapatkan nomor Array(n).fill(0).
Peter Cordes
@PeterCordes Meskipun mungkin bisa disebut lebih sederhana, saya pikir kodenya cukup panjang sehingga kurang mudah dibaca
JollyJoker
Ya, efisiensi akan membutuhkan 1 baris ekstra untuk tmp var. Jawaban saya menggunakan beberapa baris tambahan karena saya sudah terbiasa dengan C dan C ++ dan bahasa assembly, dan memiliki lebih banyak baris yang terpisah membuatnya lebih mudah untuk melacak ASM yang dihasilkan compiler kembali ke pernyataan yang bertanggung jawab untuk itu ketika mengoptimalkan / profiling. Plus, saya biasanya tidak melakukan JS jadi saya tidak merasa perlu menjejalkan semuanya ke beberapa baris. Tapi Anda bisa melakukannya let f64arr = new Float64Array(arr.filter(n => n != 0)), lalu [ ...Array(arr.length - f64arr.length).fill(0),... jadi itu menambah 1 baris ekstra dan menyederhanakan baris terakhir.
Peter Cordes
4

Anda dapat melakukan ini seperti ini:

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)

atau Anda dapat melakukan ini:

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
sumber
4
Solusi pertama Anda memiliki masalah konsistensi yang sama dengan input negatif seperti jawaban Harunur Rashid. Yang kedua adalah konsisten, tetapi memperlakukan semua angka negatif sebagai sama dengan nol, meninggalkan urutan mereka saling terdefinisi.
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);

Maks
sumber