var numArray = [4,2,5,3];
numArray.sort(function(a,b){
console.log("a:" + a + ", b:" + b);
return a-b;
});
The three possible return numbers are: <0 (less than 0), 0, or >0 (greater than 0):
Less than 0: Sort "a" to be a lower index than "b"
Zero: "a" and "b" should be considered equal, and no sorting performed.
Greater than 0: Sort "b" to be a lower index than "a".
I am getting like this on console
a:4, b:2
a:4, b:5
a:5, b:3
a:2, b:3
a:4, b:3
[2, 3, 4, 5]
May I know how the values of a and b are getting changed as seen above?
In the first step
a=4 and b=2. In this case it will swap.
then the array becomes [2,4,5,3];
In the second step
a=4 and b=5. In this case the items remains same.
then the array becomes [2,4,5,3];
In the third step
a=5 and b=3. In this case it will swap.
then the array becomes [2,4,3,5];
Until this step it is okay. But after that how the value of a and b becomes 2 and 3 > respectively instead of 2 and 4.
Anyone can help me?
Thanks in advance
There is no guarantee that the sort method uses a simple compare and swap algorithm.
More likely your browser uses something like merge sort, but the specific algorithm will depend on the browser and version.
Since Firefox is open source you can see how sort is implemented by checking the source code. It uses a merge sort.
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>
I've been trying to see what's happening exactly with this code by stepping through it in Google Chrome. How and when does the sort method move each element in this array?
I cannot figure out why "b" is index 0 and why "a" is index 1 initially. Logically a should be 10.99 and "b" should be 5.99.
And where is the data getting temporarily stored as it moves through the elements in the array?
Also, when are the elements "shifting"?. Meaning what is happening through each time the a,b parameters move to compare the next a,b set of numbers of the prices array in this sort() function?
const prices = [10.99, 5.99, 3.99, 6.59, 8.49, 1.99, 12.99];
const sortedPrices = prices.sort((a, b) => {
if (a > b) {
console.log("a is greater than b. a moves right, b moves left.");
return 1;
} else if (a === b) {
console.log("a is the same value as b.");
return 0;
} else {
console.log("a is less than b. a moves left, b moves right.");
return -1;
}
});
console.log(sortedPrices);
This image is the sorting order as I followed what numbers were compared to each other. The beginning of the arrow represents "a" and the end of the arrow with the "<" represents "b". Where it gets confusing on top of trying to understand the above issue, is why when the sort method gets to 1.99, why does it skip 8.49 and get compared to 6.59?
partially it is explained there: Javascript Array.sort implementation?
initial values are different for different implementations in different engines
look at the native code of this method, which are provided there: Javascript native sort method code
also I recommend read more about sorting algorithms
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'm reading "Professional JavaScript for Web Developers" (third edition) by Nicholas Zakas in an attempt to teach myself JS. However, I am having difficulty following the Location Methods section of chapter 5 on page 118 (in case you have the book). He explains that "the indexOf() method starts searching from the front of the array (item 0) and continues to the back, whereas lastIndexOf() starts from the last item in the array and continues to the front". Also he explains that "Each of these methods accepts two arguments: the item to look for and an optional index from which to start looking". He then attempts to illustrate this with examples.
As you can see below, to the right of the alert statements, he has listed what the correct output will be for each statement given the supplied argument(s). I do not understand how these outputs are determined. For example, how does alert(numbers.indexOf(4)); produce 3? I was reading this last night and thought I was just too tired to understand, however, I still cannot seem to figure out how this is achieved. I searched the Errata section from the book's companion website for a possible typo, but nothing was listed. I also searched elsewhere but found examples that mostly dealt with strings instead of numbers. Thanks for any help. This is my first post to stack overflow so my apologies if I have done something incorrect in my post.
His examples:
var numbers = [1,2,3,4,5,4,3,2,1];
alert(numbers.indexOf(4)); //3
alert(numbers.lastIndexOf(4)); //5
alert(numbers.indexOf(4, 4)); //5
alert(numbers.lastIndexOf(4, 4)); //3
The way I thought the outcome would be:
alert(numbers.indexOf(4));
//the item in the array with the fourth index, or 5
alert(numbers.lastIndexOf(4));
//5 (this was only one that seemed to make sense to me) by counting back from the last value
alert(numbers.indexOf(4, 4));
//start looking at index 4, or 5, and then count right four places to end up at 1 (last item in array).
alert(numbers.lastIndexOf(4, 4));
//1, counting back to the left from the value with index 4, or 5, to reach the first value in the array.
Any help in determining the outputs based on the required argument and then how to also count from a specified value given the additional optional argument would be much appreciated. Thanks again.
In most of the Programming languages, default indexing start from 0. Therefore, you have an understanding problem. Double consider your example with index starting from 0.
var numbers = [1,2,3,4,5,4,3,2,1];
alert(numbers.indexOf(4)); //3, because 4 is at 3rd index
alert(numbers.lastIndexOf(4)); //5, because last 4 is at 5th index
alert(numbers.indexOf(4, 4)); //5, because searching will start from 4th index
alert(numbers.lastIndexOf(4, 4)); //3, because searching will start from last 3rd element.
JavasScript arrays are zero indexed, in other words, the first item has an index of zero. This is true for almost all programming languages (apart fro XPath for some odd reason!).
The indexOf function returns the index of the first item it finds that equals the supplied argument.
var numbers = [1,2,3,4,5,4,3,2,1];
var index = numbers.indexOf(4); // index is 3
alert(numbers[index]); // outputs 4
In JS or many other languages the index count of array starts with 0 so for,
var numbers = [1,2,3,4,5,4,3,2,1];
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
numbers[3] = 4
numbers[4] = 5
numbers[5] = 4
numbers[6] = 3
numbers[7] = 2
numbers[8] = 1
It's
indexOf("SearchString");
not
indexOf(indexNumber);
That would be awfully redundant.