How does Javascript's sort() work?

前端 未结 9 1794
长情又很酷
长情又很酷 2020-11-22 11:06

How does the following code sort this array to be in numerical order?

var array=[25, 8, 7, 41]

array.sort(function(a,b){
  return a - b
})
相关标签:
9条回答
  • 2020-11-22 11:51

    run this code. You can see the exact step by step sorting process from the beginning to the end.

    var array=[25, 8, 7, 41]
    var count = 1;
    array.sort( (a,b) => { 
    console.log(`${count++}). a: ${a} | b: ${b}`);
    return a-b;
    });
    console.log(array);
    
    0 讨论(0)
  • 2020-11-22 11:53

    Is the array sort callback function called many times during the course of the sort?

    Yes, that's exactly it. The callback is used to compare pairs of elements in the array as necessary to determine what order they should be in. That implementation of the comparison function is not atypical when dealing with a numeric sort. Details in the spec or on some other more readable sites.

    0 讨论(0)
  • 2020-11-22 12:08

    To help clarify the behavior of Array#sort and its comparator, consider this naive insertion sort taught in beginning programming courses:

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

    Ignoring the choice of insertion sort as the algorithm, focus on the hardcoded comparator: arr[j-1] > arr[j]. This has two problems relevant to the discussion:

    1. The > operator is invoked on pairs of array elements but many things you might want to sort such as objects don't respond to > in a reasonable way (the same would be true if we used -).
    2. Even if you are working with numbers, oftentimes you want some other arrangement than the ascending sort that's been baked-in here.

    We can fix these problems by adding a comparefn argument which you're familiar with:

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

    Now the naive sort routine is generalized. You can see exactly when this callback is invoked, answering your first set of concerns:

    Is the array sort callback function called many times during the course of the sort? If so, I'd like to know which two numbers are passed into the function each time

    Running the code below shows that, yes, the function is called many times and you can use console.log to see which numbers were passed in:

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

    You ask:

    How are the two sets of numbers then sorted in relation to one another?

    To be precise with terminology, a and b aren't sets of numbers--they're objects in the array (in your example, they're numbers).

    The truth is, it doesn't matter how they're sorted because it's implementation-dependent. Had I used a different sort algorithm than insertion sort, the comparator would probably be invoked on different pairs of numbers, but at the end of the sort call, the invariant that matters to the JS programmer is that the result array is sorted according to the comparator, assuming the comparator returns values that adhere to the contract you stated (< 0 when a < b, 0 when a === b and > 0 when a > b).

    In the same sense that I have the freedom to change my sort's implementation as long as I don't breach my specification, implementations of ECMAScript are free to choose the sort implementation within the confines of the language specification, so Array#sort will likely produce different comparator calls on different engines. One would not write code where the logic relies on some particular sequence of comparisons (nor should the comparator produce side effects in the first place).

    For example, the V8 engine (at the time of writing) invokes Timsort when the array is larger than some precomputed number of elements and uses a binary insertion sort for small array chunks. However, it used to use quicksort which is unstable and would likely give a different sequence of arguments and calls to the comparator.

    Since different sort implementations use the return value of the comparator function differently, this can lead to surprising behavior when the comparator doesn't adhere to the contract. See this thread for an example.

    0 讨论(0)
提交回复
热议问题