I want to sort an array of numerical values and I find a code likes :
<!DOCTYPE html>
<html>
<body>
<p>Click the button to sort the array in ascending order.</p>
<button onclick="myFunction()">Try it</button>
<p id="demo"></p>
<script>
var points = [40, 100, 1, 5, 25, 10];
document.getElementById("demo").innerHTML = points;
function myFunction() {
points.sort(function(a, b){return a-b});
document.getElementById("demo").innerHTML = points;
}
</script>
</body>
</html>
Output:
1,5,10,25,40,100
The code runs properly but I can't understand how the statement
points.sort(function(a, b){return a-b});is working and what is a and b?
Array.sort is a function, which takes compare function as a parameter.
When sorting, sort function calls that compare function, passing in two elements of an array being sorted.
If this function returns value, which is less than zero, than first
element should stay left from the second.
If it returns number, which is greater than zero, than first element
should stay right from the second (in sorted array)
If it returns zero, than these elements are equal and it doesn't
matter in which order they stay.
So to change the order of sort (if you need), you can just switch the compare function to
return b - a;
This would be easy to understand:
if(a < b){
return -1;
} else if( a > b){
return 1;
} else {
return 0;
}
a and b are numbers in the array which are compared with each other,
Hence if a-b comes out to be negative then it signifies that b>a so if the list is ascending it will put a ahead of b
As per Mozilla Docs
If compareFunction(a, b) is less than 0, sort a to a lower index than
b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee
this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.
If compareFunction(a, b) is greater than 0, sort b to a lower index than a. compareFunction(a,
b) must always return the same value when given a specific pair of elements a and b as its two arguments.
If inconsistent results are returned then the sort order is undefined
Related
const arr = [3, 89, 1, 120, 23];
console.log(arr.sort((a, b) => a - b));
The code above sorts the array.
However, how does sort know that a = current index and that b = next index???
We never specify to sort what they are and to what they're equal to.
And after that, how does .sort figure out that the returned value from that anonymous arrow function means that the value need to be moved?
Example: [1, 2]
sort((a, b) => a - b))
sort((1, 2) => 1 - 2))
sort((1, 2) => -1))
sort(-1));
See? How does .sort know what to do with -1?
I've been Googling and YouTubing for the past 2 hours and can't find the answer... :(
However, how does sort know that a = current index and that b = next index?
No, a is current value, b is next value.
The sort function you see is not just run once, it runs again and again, depending on which length your array has.
In your case, in the first run, a equals 3 and b equals 89. And the second run, a equals 3, b equals 1.
The sort function is work for an array of strings, so if you need to sort an array of strings, just use array.sort()
Compare function is required when you need to sort a numeric array.
If not, it gets the wrong result: "25" is bigger than "100", because "2" is bigger than "1".
Compare function just return three kinds of number:
Negative number (-1,-2,...): it means a less than b
Zero (0): it means a equals b
Positive number (1,2,3..): it means a greater than b
So, you can more clearly return inside the Compare function like this:
if(a === b)
return 0;
if(a < b)
return -1;
return 1;
But, simpler, you can a using a trick, you just need to return a-b to determine whether a is greater than b or not.
See more: JavaScript Sorting Arrays
The following shuffle function is something I found to be brief and easy to follow.
function shuffleArray(arr) {
arr.sort(() => {
r = Math.random() - 0.5
// console.log(r)
return r
});
}
let arr = [1, 2, 3, 4, 5];
shuffleArray(arr);
console.log(JSON.stringify(arr))
Why does the included sort call need to be offset by 0.5. Yes, I have seen this before, but no, I can not convince nor understand myself why...
In fact, to confirm, removal of - 0.5 from the random range shift computation produces the exact same array that we need to shuffle.
Is there something deeper I am missing? Why do the numbers generated have to be BOTH, negative, AND positive, and why by a 0.5 offset specifically?
If you want to look behind the curtain an analyze this through the lens of the particular sorting algorithm being used, you can't. The ECMAscript standard does not specify which sort algorithm browsers have to use for JavaScript's sort algorithm, so Firefox might do it one way while Chrome does it another. And then Chrome might change to do it differently later down the line. We just have to accept that level of abstraction.
Now, the are things that we do know. JavaScript allows us to provide a "comparison" function to be used when sorting arrays. you can pass two arguments into that comparison function and return a result to signal which value should appear first in the resulting array.
Returning a negative number signals that the first argument should occur earlier in the sorted array.
Returning a positive number signals that the second argument should occur earlier in the sorted array.
Returning zero signals that the two values are the same and should appear next to each other in the sorted array.
Here's a little snippet to exemplify these rules:
var arr = ["w", "l", "w"];
arr.sort(function(a, b){
if(a === b) return 0; //they're the same
if(a === "w") return -1; //w should appear earlier
return 1; //l should appear later
});
console.log(arr);
We can change this to work with numbers as well:
var arr = [1, 3, 1];
arr.sort(function(a, b){
if(a === b) return 0; //they're the same
if(a < b) return -1; //lower numbers should appear earlier
return 1; //higher numbers should appear later
});
console.log(arr);
So at this point, an interesting question is "What happens if I just always return a positive number?" Well, what you're really telling the sorting algorithm by doing that is that everything should appear later in the sorted array. But what is "later" without anything earlier?! Is it just going to keep looping forever? will my computer explode?
The answer is, that totally depends on the sorting algorithm that the browser chooses to use. You can pretty well trust that the browser's sorting algorithm is going to pick a place to stop sorting and call it a day, but where the chips lie at that time can vary based on the browser. Try this example in Chrome and Firefox, and you'll probably get different results:
var arr = [8, 6, 7, 5, 3, 0, 9];
arr.sort((a, b) => 1);
console.log(arr);
And because any positive number returned in a comparison function signals the same thing, always returning Math.random() will have the same effect (because you can pretty much guarantee that random number will be greater than zero):
var arr = [8, 6, 7, 5, 3, 0, 9];
arr.sort((a, b) => Math.random());
console.log(arr);
It's only when you introduce the possibility of returning a negative number that you starting telling the sorting algorithm to show some values earlier than others in the sorted array:
var arr = [8, 6, 7, 5, 3, 0, 9];
arr.sort((a, b) => Math.random() - .5);
console.log(arr);
As for why we choose .5 specifically? Well, Math.random gives you a number 0 to 1. That's a positive number roughly 100% of the time. If we subtract .1 from Math.random, we start getting results from -0.1 to 0.9. That's a positive number roughly 90% of the time! It would work, but ideally, we want it to be more of a coin flip so we can be happier with the fairness of the random sort. That's why we subtract 0.5 specifically- to get numbers from -0.5 to 0.5, yielding a positive result roughly 50% of the time and a negative result for roughly the other 50% of the time.
But if you care about the quality of the results...
As others have mentioned, the above comparison function has been thoroughly tested and is known to favor some numbers over others. The most popular correct shuffle is called the Fisher Yates shuffle, and you can read about it here. It's definitely not as concise, and when used with Math.random, it's of course not cryptographically secure.
If you need a cryptographically strong random sort but still want something short and sweet, I always recommend having a look at rando.js. You can get a cryptographically strong sort just like this:
console.log(randoSequence([8, 6, 7, 5, 3, 0, 9]));
<script src="https://randojs.com/2.0.0.js"></script>
It keeps track of the original indices by default in case two values are the same but you still care where they came from. If you dont like that, you whittle the result down to just the values with a simple map:
console.log(randoSequence([8, 6, 7, 5, 3, 0, 9]).map((i) => i.value));
<script src="https://randojs.com/2.0.0.js"></script>
When sorting an array of numbers in JavaScript, I accidentally used < instead of the usual - -- but it still works. I wonder why?
Example:
var a = [1,3,2,4]
a.sort(function(n1, n2){
return n1<n2
})
// result is correct: [4,3,2,1]
And an example array for which this does not work (thanks for Nicolas's example):
[1,2,1,2,1,2,1,2,1,2,1,2]
This sort works on your input array due to its small size and the current implementation of sort in Chrome V8 (and, likely, other browsers).
The return value of the comparator function is defined in the documentation:
If compareFunction(a, b) is less than 0, sort a to an index lower than
b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and
b unchanged with respect to each other, but sorted with respect to all
different elements.
If compareFunction(a, b) is greater than 0, sort b to an index lower than a, i.e. b comes first.
However, your function returns binary true or false, which evaluate to 1 or 0 respectively when compared to a number. This effectively lumps comparisons where n1 < n2 in with n1 === n2, claiming both to be even. If n1 is 9 and n2 is 3, 9 < 3 === false or 0. In other words, your sort leaves 9 and 3 "unchanged with respect to each other" rather than insisting "sort 9 to an index lower than 3".
If your array is shorter than 11 elements, Chrome V8's sort routine switches immediately to an insertion sort and performs no quicksort steps:
// Insertion sort is faster for short arrays.
if (to - from <= 10) {
InsertionSort(a, from, to);
return;
}
V8's insertion sort implementation only cares if the comparator function reports b as greater than a, taking the same else branch for both 0 and < 0 comparator returns:
var order = comparefn(tmp, element);
if (order > 0) {
a[j + 1] = tmp;
} else {
break;
}
Quicksort's implementation, however, relies on all three comparisons both in choosing a pivot and in partitioning:
var order = comparefn(element, pivot);
if (order < 0) {
// ...
} else if (order > 0) {
// ...
}
// move on to the next iteration of the partition loop
This guarantees an accurate sort on arrays such as [1,3,2,4], and dooms arrays with more than 10 elements to at least one almost certainly inaccurate step of quicksort.
Update 7/19/19: Since the version of V8 (6) discussed in this answer, implementation of V8's array sort moved to Torque/Timsort in 7.0 as discussed in this blog post and insertion sort is called on arrays of length 22 or less.
The article linked above describes the historical situation of V8 sorting as it existed at the time of the question:
Array.prototype.sort and TypedArray.prototype.sort relied on the same Quicksort implementation written in JavaScript. The sorting algorithm itself is rather straightforward: The basis is a Quicksort with an Insertion Sort fall-back for shorter arrays (length < 10). The Insertion Sort fall-back was also used when Quicksort recursion reached a sub-array length of 10. Insertion Sort is more efficient for smaller arrays. This is because Quicksort gets called recursively twice after partitioning. Each such recursive call had the overhead of creating (and discarding) a stack frame.
Regardless of any changes in the implementation details, if the sort comparator adheres to standard, the code will sort predictably, but if the comparator doesn't fulfill the contract, all bets are off.
After my initial comment, I wondered a little bit about how easy it is to find arrays for which this sorting method fails.
I ran an exhaustive search on arrays of length up to 8 (on an alphabet of size the size of the array), and found nothing. Since my (admittedly shitty) algorithm started to be too slow, I changed it to an alphabet of size 2 and found that binary arrays of length up to 10 are all sorted properly. However, for binary arrays of length 11, many are improperly sorted, for instance [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0].
// Check if 'array' is properly sorted using the "<" comparator function
function sortWorks(array) {
let sorted_array = array.sort(function(n1, n2) {
return n1 < n2;
});
for (let i=0; i<sorted_array.length-1; i++) {
if (sorted_array[i] < sorted_array[i+1]) return false;
}
return true;
}
// Get a list of all arrays of length 'count' on an alphabet of size 'max_n'.
// I know it's awful, don't yell at me :'(
var arraysGenerator;
arraysGenerator = function (max_n, count) {
if (count === 0) return [[]];
let arrays = arraysGenerator(max_n, count-1);
let result = [];
for (let array of arrays) {
for (let i = 0; i<max_n; i++) {
result.push([i].concat(array));
}
}
return result;
}
// Check if there are binary arrays of size k which are improperly sorted,
// and if so, log them
function testArraysOfSize(k) {
let arrays = arraysGenerator(2, k);
for (let array of arrays) {
if (!sortWorks(array)) {
console.log(array);
}
}
}
I'm getting some weird false-positives for some reason though, not sure where my mistake is.
EDIT:
After checking for a little while, here's a partial explanation on why OP's "wrong" sorting method works for lengths <=10 and for lengths >=11: it looks like (at least some) javascript implementations use InsertionSort if the array length is short (length <= 10) and QuickSort otherwise. It looks like QuickSort actively uses the "-1" outputs of the compare function while InsertionSort does not and relies only on the "1" outputs.
Source: here, all thanks to the original author.
If we analyze what's being done, it seems that this is mostly luck as in this case, 3 and 2 are considered to be "the same" and should be interchangeable. I suppose in such cases, the JS engines keep the original order for any values that have been deemed equal:
let a = [1, 3, 2, 4];
a.sort((n1, n2) => {
const result = n1 < n2;
if (result < 0) {
console.log(`${n1} comes before ${n2}`);
} else if (result > 0) {
console.log(`${n2} comes before ${n1}`);
} else {
console.log(`${n1} comes same position as ${n2}`);
}
return result;
})
console.log(a);
As pointed out in the comments, this isn't guaranteed to work ([1,2,1,2,1,2,1,2,1,2,1,2] being a counter-example).
Depending on exact sort() implementation, it is likely that it never checks for -1. It is easier and faster, and it makes no difference (as sorting is not guaranteed to be stable anyway, IIRC).
If the check sort() makes internally is compareFunction(a, b) > 0, then effectively true is interpreted as a > b, and false is interpreted as a <= b. And then your result is exactly what one would expect.
Of course the key point is that for > comparison true gets covered to 1 and false to 0.
Note: this is all speculation and guesswork, I haven't confirmed it experimentally or in browser source code - but it's reasonably likely to be correct.
Sort function expects a comparator which returns a number (negative, zero, or positive).
Assuming you're running on top of V8 engine (Node.js, Chrome etc.), you can find in array implementation that the returned value is compared to 0 (yourReturnValue > 0). In such case, the return value being casted to a number, so:
Truthy values are converted to 1
Falsy values are converted to 0
So based the documentation and the above, your function will return a sorted array in descending order in your specific case, but might brake in other cases since there's no regard to -1 value.
I know about sorting arrays, but I have the question I'm aching to find out.
For instance:
var array = [9,3,0,-2,15];
So, let's sort it:
function sortFunction(a, b){
if(a < b) return -1; // or any number that less than zero, but why?
if(a > b) return 1; // or any number that above zero, but why?
if(a = b) return 0; // but why?
// I know it may be easier a - b, I've written it for clarity
}
array.sort(sortFunction); //so we'll get correct result
How to understand why it has to return -1, 1, and 0 for sorting array?
P.S. Sorry if question seems stupid, I haven't found the answer in Google.
From MDN:
If compareFunction(a, b) is less than 0, sort a to an index lower than b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different
elements. Note: the ECMAscript standard does not guarantee this
behaviour, and thus not all browsers (e.g. Mozilla versions dating
back to at least 2003) respect this.
If compareFunction(a, b) is greater than 0, sort b to a lower index than a.
There are three outcomes, so three possible return values.
This code snippet is from w3schools JavaScript section. I am trying to figure out what
points.sort( function(a, b) {
return 0.5 - Math.random()
});
from the code below does. I understand it is trying to perform a random sort on the numbers stored in the array called points, but I don't understand how it is achieved with return 0.5 - Math.random().
I know that random returns a number between 0 and 1 (not including 1).
I supposed that 0.5 is then subtracted from that number, but I am not sure what happens from here. Could you kindly give me a step by step explanation?
<!DOCTYPE html>
<html>
<body>
<p>Click the button (again and again) to sort the array in random order.</p>
<button onclick="myFunction()">Try it</button>
<p id="demo"></p>
<script>
var points = [40, 100, 1, 5, 25, 10];
document.getElementById("demo").innerHTML = points;
function myFunction() {
points.sort(function(a, b){return 0.5 - Math.random()});
document.getElementById("demo").innerHTML = points;
}
</script>
</body>
Passing a callback to Array#sort() that returns a random value is a bad idea; the ECMAScript spec provides no guarantees about what will happen in that case. Per MDN:
compareFunction(a, b) must always return the same value when given a specific pair of elements a and b as its two arguments. If inconsistent results are returned then the sort order is undefined.
And per the spec itself:
If comparefn is not undefined and is not a consistent comparison function for the elements of this array (see below), the sort order is implementation-defined.
The W3Schools code from the question is demonstrably broken; it doesn't shuffle the array fairly, at least in Chrome. Let's try running it a million times and counting how often each value shows up in the final position in the array after "shuffling":
function shuffleAndTakeLastElement() {
var points = [40, 100, 1, 5, 25, 10];
return points.sort(function(a, b){return 0.5 - Math.random()})[5];
}
results = {};
for (var point of [40, 100, 1, 5, 25, 10]) results[point] = 0;
for (var i = 0; i < 1000000; i++) results[shuffleAndTakeLastElement()]++;
console.log(results);
I get the following counts in Chrome:
{1: 62622, 5: 125160, 10: 500667, 25: 249340, 40: 31057, 100: 31154}
Notice how the number 10 is around 16 times more likely to end up in the end position of the array than the numbers 40 or 100 are. This ain't a fair shuffle!
A few morals to draw from this story:
You should run a large number of tests and look at the results to help confirm whether any randomness algorithm is fair.
It's easy to accidentally write a biased algorithm even if you're starting with a fair source of randomness.
For shuffling arrays, use Lodash's _.shuffle method or one of the approaches from How to randomize (shuffle) a JavaScript array?.
Never trust anything you read on W3Schools, because they suck and are riddled with errors.
The sort callback is supposed to return a value <0, 0 or >0 to indicate whether the first value is lower than, equal to or higher than the second; sort uses that to sort the values. 0.5 - Math.random() returns a value between -0.5 and 0.5 randomly, satisfying the expected return values and resulting in an essentially randomly shuffled array.
Note that this shouldn't be the preferred method to shuffle; since the return value is random and not internally consistent (e.g. it tells sort that foo < bar, bar < baz and foo > baz), it may make Javascript's sort algorithm very inefficient. A Fisher-Yates shuffle for instance is pretty trivially implemented and likely more efficient.