เพื่อช่วยชี้แจงพฤติกรรมของ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);
ละเว้นทางเลือกของการจัดเรียงแทรกเป็นอัลกอริทึมที่มุ่งเน้นที่hardcodedarr[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 คืออาร์เรย์ผลลัพธ์จะเรียงตาม ตัวเปรียบเทียบสมมติว่าตัวเปรียบเทียบจะส่งคืนค่าที่เป็นไปตามสัญญาที่คุณระบุไว้ (<0 เมื่อa < b
0 เมื่อใดa === b
และ> 0 เมื่อใดa > b
)
ในทำนองเดียวกันว่าฉันมีอิสระในการเปลี่ยนแปลงการใช้งานการเรียงลำดับของฉันตราบใดที่ฉันไม่ละเมิดข้อกำหนดของฉันการใช้ ECMAScript มีอิสระที่จะเลือกการใช้งานการเรียงลำดับภายในขอบเขตของข้อกำหนดภาษาดังนั้นจึงArray#sort
มีแนวโน้มที่จะสร้างการเรียกตัวเปรียบเทียบที่แตกต่าง บนเครื่องยนต์ที่แตกต่างกัน ไม่มีใครเขียนโค้ดที่ตรรกะขึ้นอยู่กับลำดับของการเปรียบเทียบโดยเฉพาะ (และตัวเปรียบเทียบไม่ควรสร้างผลข้างเคียงตั้งแต่แรก)
ตัวอย่างเช่นเครื่องยนต์ V8 (ในขณะที่เขียน) เรียกใช้Timsortเมื่ออาร์เรย์มีขนาดใหญ่กว่าจำนวนองค์ประกอบที่คำนวณไว้ล่วงหน้าและใช้การเรียงลำดับการแทรกไบนารีสำหรับส่วนอาร์เรย์ขนาดเล็ก อย่างไรก็ตามมันเคยใช้Quicksortซึ่งไม่เสถียรและมีแนวโน้มที่จะให้ลำดับอาร์กิวเมนต์ที่แตกต่างกันและการเรียกใช้ตัวเปรียบเทียบ
เนื่องจากการใช้งานการเรียงลำดับที่แตกต่างกันใช้ค่าส่งคืนของฟังก์ชันตัวเปรียบเทียบที่แตกต่างกันสิ่งนี้อาจนำไปสู่พฤติกรรมที่น่าแปลกใจเมื่อตัวเปรียบเทียบไม่ปฏิบัติตามสัญญา ดูหัวข้อนี้สำหรับตัวอย่าง