I noticed IE9 sort order is changing elements order when comparison function returns 0.
See:
var myarray=[
{id:1,val:0},
{id:2,val:0},
{id:3,val:7},
{id:4,val:41}
];
myarray.sort(function(a,b){return a.val - b.val});
for(var i in myarray)
{
console.log(myarray[i].id);
}
Current stable versions of Chrome, Firefox, Opera and Safari got the following output: 1 2 3 4.
Same output for IE7 and IE8.
IE9 output is: 2 1 3 4
Why? Is that normal?
Don't use for...in on an array if you're trying to iterate over the numeric properties, for two reasons:
You will also get methods and properties added to Array.prototype showing up;
The iteration order is defined in the ECMAScript spec as being implementation-dependent, meaning it could in theory be anything.
Both points also apply to Objects. Chrome in fact does not conform to the most common browser behaviour, leading to heated debate in a Chrome bug report.
From MDC (emphasis mine):
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.
In my experience, only Chrome/Firefox get this right. Opera 11's behavior for me is .. not well defined.
E.g., using sort to move all zeroes to the top of the array:
[1, 0, 3, 0, 5, 0, 2].sort(function (a, b) { return b === 0 && 1 || 0;});
Chromium 10: [0, 0, 0, 1, 3, 5, 2]
Firefox 4: [0, 0, 0, 1, 3, 5, 2]
Opera 11: [0, 0, 0, 2, 1, 5, 3] <- does not maintain order of non-zeroes
Based on your sort function, both of those elements are equal and it shouldn't matter which order they appear in. It is up to the browser to either leave the order as it is or switch the order as it sees appropriate...neither is a guarantee.
If the two aren't equal, then your sort function is incorrect and should take the other items into account as well.
Related
While looking over new changes to JavaScript I noticed that Set and Map use .size instead of .length like arrays would.
This seems like a pointless diversion from what's normal with arrays - just one more thing to remember.
Was there a good design reason for this?
There's a lot of discussion in the esdiscuss thread "Set length property". This was a hotly debated issue, so it's not surprising that you do not necessarily agree with the resolution.
There is a tremendous amount of arguing about this in esdiscuss. Ultimately, the argument that prevailed (as evidenced by the fact that ES2015's Sets have size and not length) was summarized in a post by David Bruant:
...for me 'length' refers to a measurement with something like a ruler. You start at 0 and see up to where it goes. This is very accurate for an array which is an indexed set (starting at 0 and growing) and for arrays as considered in C (continuous sequence of bytes) which ECMAScript arrays seem inspired of. This is probably less relevant for unordered collections such as sets which I'd tend to consider as a messy bag.
And further discussed in a post by Dean Landolt:
Just wanted to jump in and say non-writable length is consistent with String behavior as well, but David makes a good point about length implying metric topology. David's suggestion of count is nice. ISTM what we're talking about is cardinality, but no need to get too silly w/ precision. Though size is just fine with me, and has plenty of prior art.
While apsillers' Jan 27, 2016 answer adds great links, a code example is missing. The size of a set is a read-only getter while that's not the case for arrays which allow modifying the length to truncate the array.
let arr = [1, 2, 3, 4]
arr.length = 2
console.log("modified length array", arr) // [1, 2]
let mySet = new Set([1, 2, 3, 4])
mySet.length = 2
mySet.size = 2
console.log("modified length set", [...mySet]) // [1, 2, 3, 4]
let str = "1234"
str.length = 2
console.log("modified length string", str) // "1234"
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>
Was playing around with some code to create an array of 0's and found that for only one value NaN was returned instead of 0. I get this in Chrome, Node, and Firefox.
What's causing the second value to be NaN?
var arr = new Array(32).join(0).split('').map(parseInt)
// prints [0, NaN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
console.dir(arr)
That's because the function passed to map will be called with three arguments:
The current array item
The index of that item
The whole array
In this case, that function is parseInt, which only uses two arguments:
The string to be parsed
The radix used to parse the string
Additional arguments will be ignored.
Therefore, for the 2nd item in the array (i.e. index 1), the call will be
parseInt("0", 1, ignoredArray)
When the radix is 1, NaN is returned:
Let R = ToInt32(radix).
If R ≠ 0, then
If R < 2 or R > 36, then return NaN.
Also note that if you used a bigger number like new Array(99), you would have seen NaNs starting at index 37.
The .map() function passes three arguments to the callback of which two are used by parseInt(): the value, and the index (the third is the array itself). When the index is 1, any string of digits will be an invalid value. The parseInt() function ignores the second argument when it's 0.
To elaborate: for the first element, parseInt() is called like this:
parseInt("0", 0)
because the array value is zero and the index is zero. On the second call, it's this:
parseInt("0", 1)
and that returns NaN.
Note that if you're not too picky about the results being all integers, you can do what you're trying to do with the Number constructor:
var arr = new Array(32).join(0).split('').map(Number);
In ES2015 (the newest ratified standard for JavaScript, which if we are all very good will be fully implemented in all browsers as our Christmas present) you can use the .fill() function:
var arr = new Array(31).fill(0);
The mapping passes three arguments to its function:
the element;
the index of that element within the array; and
the array itself.
The parseInt function will look at the first two of those, treating the first correctly as the string but treating the second as the base to use. When the base is neither zero nor in the inclusive range 2..36, it returns NaN (1). If you had an array with forty elements, you'd also see a bunch of NaN values at the end:
0, NaN, 0, 0, 0, ... 0, 0, 0, NaN, NaN
You'd also get some pretty strange results if the number strings were anything other than zero, since the array index would dictate what base was used to interpret it.
To actually fix this, you can just provide a function that will translate what map gives you to what parseInt expects (a map map, I guess you could call it):
function myParseInt(s,r,a) { return parseInt(s,10); }
var arr = new Array(32).join(0).split('').map(myParseInt)
alert(arr)
You might also want to have a look at the way you're creating this array, it will actually end up as an array of size 31 rather than 32. If you just want an array of '0' characters, you can just use:
var arr = new Array(32).fill('0')
assuming you have a browser that supports ECMAScript 2015, which is Safari, Firefox and Chrome-desktop as of the time of this answer.
(1) A base of zero is the default case for handling things like hex prefixes.
A base of one makes no sense in a purely positional system (where each digit is multiplied by a power of the base and accumulated) since the only allowed digit there would be 0, so each place value would be that zero multiplied by 1n. In other words, the only number possible in a base-one system would be zero.
Bases from 2 to 36 are therefore more sensible.
In attempting to build a WebGL 3D library for myself (learning purposes mostly) I followed documentation that I found from various sources that stated that the TypedArray function set() (specifically for Float32Array), is supposed to be "as fast as" memcpy in C (obviously tongue in cheek), literally the fastest according to html5rocks. On appearances that seemed to be correct (no loop setup in javascript, disappearing into some uberfast typed array pure C nonsense, etc).
I took a gander at glMatrix (good job on it btw!), and noticed that he (author) stated that he unrolled all of the loops for speed. This is obviously something a javascript guru would do normally for as much speed as possible, but, based on my previous reading, I thought I had 1-up on this library, specifically, he created his lib to be functional with both arrays and typed arrays, thus I thought that I would get more speed by using set() since I was only interested in staying in TypedArray types.
To test my theory I set up this jsperf. Not only does set() comparatively lack speed, every other technique I tried (in the jsperf), beats it. It is the slowest by far.
Finally, my question: Why? I can theoretically understand a loop unrolling becoming highly optimized in spidermonkey or chrome V8 js-engines, but losing out to a for loop seems ridiculous (copy2 in jsperf), especially if its intent is theoretically to speed up copies due to the raw contiguous in memory data types (TypedArray). Either way it feels like the set() function is broken.
Is it my code? my browser? (I am using Firefox 24) or am I missing some other theory of optimization? Any help in understanding this contrary result to my thoughts and understandings would be incredibly helpful.
This is an old question, but there is a reason to use TypedArrays if you have a specific need to optimize some poorly performing code. The important thing to understand about TypedArray objects in JavaScript is that they are views which represent a range of bytes inside of an ArrayBuffer. The underlying ArrayBuffer actually represents the contiguous block of binary data to operate on, but we need a view in order to access and manipulate a window of that binary data.
Separate (or even overlapping) ranges in the same ArrayBuffer can be viewed by multiple different TypedArray objects. When you have two TypedArray objects that share the same ArrayBuffer, the set operation is extremely fast. This is because the machine is working with a contiguous block of memory.
Here's an example. We'll create an ArrayBuffer of 32 bytes, one length-16 Uint8Array to represent the first 16 bytes of the buffer, and another length-16 Uint8Array to represent the last 16 bytes:
var buffer = new ArrayBuffer(32);
var array1 = new Uint8Array(buffer, 0, 16);
var array2 = new Uint8Array(buffer, 16, 16);
Now we can initialize some values in the first half of the buffer:
for (var i = 0; i < 16; i++) array1[i] = i;
console.log(array1); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
console.log(array2); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
And then very efficiently copy those 8 bytes into the second half of the buffer:
array2.set(array1);
console.log(array1); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
console.log(array2); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
We can confirm that the two arrays actually share the same buffer by looking at the buffer with another view. For example, we could use a length-8 Uint32Array that spans the entire 32 bytes of the buffer:
var array3 = new Uint32Array(buffer)
console.log(array3); // [50462976, 117835012, 185207048, 252579084,
// 50462976, 117835012, 185207048, 252579084]
I modified a JSPerf test I found to demonstrate the huge performance boost of a copy on the same buffer:
http://jsperf.com/typedarray-set-vs-loop/3
We get an order of magnitude better performance on Chrome and Firefox, and it's even much faster than taking a normal array of double length and copying the first half to the second half. But we have to consider the cycles/memory tradeoff here. As long as we have a reference to any single view of an ArrayBuffer, the rest of the buffer's data can not be garbage collected. An ArrayBuffer.transfer function is proposed for ES7 Harmony which would solve this problem by giving us the ability explicitly release memory without waiting for the garbage collector, as well as the ability to dynamically grow ArrayBuffers without necessarily copying.
Well set doesn't exactly have simple semantics like that, in V8 after doing some figuring out of what should be done it will essentially arrive at exactly the same loop that the other methods are directly doing in the first place.
Note that JavaScript is compiled into highly optimized machine code if you play your cards right (all the tests do that) so there should be no "worshipping" of some methods just because they are "native".
I've also been exploring how set() performs and I have to say that for smaller blocks (such as the 16 indices used by the original poster), set() is still around 5x slower than the comparable unrolled loop, even when operating on a contiguous block of memory.
I've adapted the original jsperf test here. I think its fair to say that for small block transfers such as this, set() simply can't compete with unrolled index assignment performance. For larger block transfers (as seen in sbking's test), set() does perform better but then it is competing with literally 1 million array index operations so it would seem bonkers to not be able to overcome this with a single instruction.
The contiguous buffer set() in my test does perform slightly better than the separate buffer set(), but again, at this size of transfer the performance benefit is marginal
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.