Untuk membantu memperjelas perilaku Array#sort
dan pembandingnya, pertimbangkan jenis penyisipan naif ini yang diajarkan di kursus pemrograman awal:
const sort = arr => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && arr[j-1] > arr[j]; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array);
console.log("" + array);
Mengabaikan pilihan insertion sort sebagai algoritma, fokus pada hardcoded pembanding: arr[j-1] > arr[j]
. Ini memiliki dua masalah yang relevan dengan diskusi:
- The
>
Operator dipanggil pada pasang elemen array tetapi banyak hal yang mungkin ingin mengurutkan seperti benda tidak menanggapi >
dengan cara yang wajar (sama akan menjadi kenyataan jika kita menggunakan -
).
- Bahkan jika Anda bekerja dengan angka, seringkali Anda menginginkan pengaturan lain selain jenis menaik yang telah dipanggang di sini.
Kami dapat memperbaiki masalah ini dengan menambahkan comparefn
argumen yang Anda kenal:
const sort = (arr, comparefn) => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array, (a, b) => a - b);
console.log("" + array);
sort(array, (a, b) => b - a);
console.log("" + array);
const objArray = [{id: "c"}, {id: "a"}, {id: "d"}, {id: "b"}];
sort(objArray, (a, b) => a.id.localeCompare(b.id));
console.log(JSON.stringify(objArray, null, 2));
Sekarang jenis rutinitas yang naif digeneralisasikan. Anda dapat melihat dengan tepat kapan callback ini dipanggil, menjawab rangkaian masalah pertama Anda:
Apakah fungsi callback sortir array dipanggil berkali-kali selama pengurutan? Jika demikian, saya ingin tahu dua angka mana yang diteruskan ke fungsi setiap kali
Menjalankan kode di bawah ini menunjukkan bahwa, ya, fungsinya dipanggil berkali-kali dan Anda dapat menggunakan console.log
untuk melihat nomor mana yang diteruskan:
const sort = (arr, comparefn) => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
console.log("on our version:");
const array = [3, 0, 4, 5];
sort(array, (a, b) => console.log(a, b) || (a - b));
console.log("" + array);
console.log("on the builtin:");
console.log("" +
[3, 0, 4, 5].sort((a, b) => console.log(a, b) || (a - b))
);
Anda bertanya:
Bagaimana kedua kumpulan angka tersebut kemudian diurutkan dalam hubungannya satu sama lain?
Tepatnya dengan terminologi, a
dan b
bukan kumpulan angka - mereka adalah objek dalam array (dalam contoh Anda, mereka adalah angka).
Sebenarnya, tidak masalah bagaimana mereka diurutkan karena bergantung pada implementasi. Seandainya saya menggunakan algoritme pengurutan yang berbeda dari jenis penyisipan, pembanding mungkin akan dipanggil pada pasangan angka yang berbeda, tetapi di akhir panggilan pengurutan, invarian yang penting bagi pemrogram JS adalah bahwa larik hasil diurutkan sesuai dengan pembanding, dengan asumsi pembanding mengembalikan nilai yang sesuai dengan kontrak yang Anda nyatakan (<0 saat a < b
, 0 saat a === b
dan> 0 saat a > b
).
Dalam arti yang sama bahwa saya memiliki kebebasan untuk mengubah implementasi sortir saya selama saya tidak melanggar spesifikasi saya, implementasi ECMAScript bebas untuk memilih implementasi sortir dalam batasan spesifikasi bahasa , jadi Array#sort
kemungkinan akan menghasilkan panggilan pembanding yang berbeda di mesin yang berbeda. Seseorang tidak akan menulis kode di mana logika bergantung pada beberapa urutan perbandingan tertentu (pembanding juga tidak harus menghasilkan efek samping di tempat pertama).
Misalnya, mesin V8 (pada saat penulisan) memanggil Timsort ketika larik lebih besar dari beberapa jumlah elemen yang dihitung sebelumnya dan menggunakan semacam penyisipan biner untuk potongan larik kecil. Namun, biasanya menggunakan quicksort yang tidak stabil dan kemungkinan akan memberikan urutan argumen dan panggilan yang berbeda ke pembanding.
Karena penerapan pengurutan yang berbeda menggunakan nilai hasil fungsi pembanding secara berbeda, hal ini dapat menyebabkan perilaku yang mengejutkan saat pembanding tidak mematuhi kontrak. Lihat utas ini sebagai contoh.