Щоб допомогти прояснити поведінку Array#sort
та його компаратор, розглянемо цей наївний сорт вставки, який викладали на початкових курсах програмування:
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);
Чи не звертаючи уваги на вибір вставки роду в якості алгоритму, фокусу на закодованому компараторе: arr[j-1] > arr[j]
. Це має дві проблеми, що стосуються дискусії:
>
Оператор викликається на пари елементів масиву , але багато речей , які ви , можливо , захочете сортувати такі як об'єкти не відповідають >
в розумних межах (те ж саме було б правильно , якби ми використовували -
).
- Навіть якщо ви працюєте з числами, часто ви хочете отримати інше розташування, крім зростаючого сорту, який тут запечений.
Ми можемо виправити ці проблеми, додавши comparefn
аргумент, який вам знайомий:
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));
Тепер узагальнена процедура наївного сортування. Ви можете точно побачити, коли викликається цей зворотний виклик, відповідаючи на свої перші проблеми:
Чи викликається функція зворотного виклику масиву багато разів протягом сортування? Якщо так, я хотів би знати, які два числа передаються у функцію кожного разу
Запуск коду нижче показує, що так, функція викликається багато разів, і ви можете використовувати, console.log
щоб побачити, які числа були передані:
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))
);
Ви запитаєте:
Як потім сортуються два набори чисел по відношенню один до одного?
Точніше з термінологією, a
а b
не набори чисел - це об’єкти в масиві (у вашому прикладі це числа).
Правда в тому, що не має значення, як вони сортуються, оскільки це залежить від реалізації. Якби я використовував інший алгоритм сортування, ніж вставка сортування, компаратор, мабуть, буде викликаний на різних парах чисел, але в кінці виклику сортування інваріант, який має значення для програміста JS, полягає в тому, що масив результатів сортується відповідно до comparator, припускаючи, що компаратор повертає значення, що відповідають заявленому вами договору (<0, колиa < b
, 0 коли a === b
і> 0 коли a > b
).
У тому самому розумінні, що я маю свободу змінювати реалізацію мого сортування, доки я не порушую свою специфікацію, реалізації ECMAScript можуть вільно вибирати реалізацію сортування в межах специфікації мови. , тому Array#sort
, ймовірно, будуть вироблятися різні виклики порівняння на різних двигунах. Не можна писати код там, де логіка покладається на певну послідовність порівнянь (і компаратор не повинен спочатку викликати побічні ефекти).
Наприклад, движок V8 (на момент написання) викликає Timsort, коли масив перевищує деяку попередньо обчислену кількість елементів, і використовує двійкове сортування вставки для невеликих фрагментів масиву. Однак раніше він використовував швидку сорту, яка є нестабільною і, швидше за все, даватиме іншу послідовність аргументів та викликів до компаратора.
Оскільки різні реалізації сортування по-різному використовують повернене значення функції порівняння, це може призвести до дивовижної поведінки, коли компаратор не дотримується договору. Див. Цей приклад для прикладу.