1

I got this piece of code from a website for randomizing a list of items. [This is for a music player that randomizes the songs (list) in every new visit by the user, thus not boring them by starting with the same song that they heard in the past visit]

    $(document).ready(function(){
      $('ul').each(function(){
            // get current ul
            var $ul = $(this);
            // get array of list items in current ul
            var $liArr = $ul.children('li');
            // sort array of list items in current ul randomly
            $liArr.sort(function(a,b){
                  // Get a random number between 0 and 10
                  var temp = parseInt( Math.random()*10 );
                  // Get 1 or 0, whether temp is odd or even
                  var isOddOrEven = temp%2;
                  // Get +1 or -1, whether temp greater or smaller than 5
                  var isPosOrNeg = temp>5 ? 1 : -1;
                  // Return -1, 0, or +1
                  return( isOddOrEven*isPosOrNeg );
            })
            // append list items to ul
            .appendTo($ul);            
      });
});

This works perfectly fine when I open my site in Chrome browser. The sorting is done pretty heavily and hence I could see that the list is randomized to a great extent.

But in Firefox and IE, the sorting is not happening to a good extent. I see that the first list item remains first in 7 out of 10 tries. And I could see the same for many other items. Ex: Item #5 occur in 3rd position in 5 out of 10 tries. With these observations, I could tell that the JS code is not working properly in IE and Firefox. (may be due to the way different browsers treat JS code because of the differences in the engine)

Now, Is there anything I can change in the JS code to make it work in all browsers?

Or Is there any other better sorting algorithm that would do a decent sorting in all browsers when implemented using JS?

I understand that a part of my 2nd question has been answered in other questions within SE but I couldn't find about the 'browser compatibility' part in those questions.

Thanks for helping.

Janaaaa
  • 1,346
  • 1
  • 16
  • 34

3 Answers3

3

Your randomisation is not very good:

// Get a random number between 0 and 10
var temp = parseInt( Math.random()*10 );
// Get 1 or 0, whether temp is odd or even
var isOddOrEven = temp%2;
// Get +1 or -1, whether temp greater or smaller than 5
var isPosOrNeg = temp>5 ? 1 : -1;
// Return -1, 0, or +1
return( isOddOrEven*isPosOrNeg );

That means in a half of the cases you return 0, which tells the sort function that the two items are equal. A good sort function will notice that and not ask again. With only a few items, you have a very high chance of the items not getting sorted well. You can proof this by counting (logging) how often your compare-function is called.

Also you don't need to return only +1, 0 or -1, you can just return any positive or negative number (see also below). For randomizing, you never really need to return equality. So, use this instead:

….sort( function() {
     return 0.5 - Math.random(); // that would be enough!
} );

However, one should not use sort for randomisation at all. Math.random does not lead to a total ordering, and sort is not made for that: (from the EcmaScript specification)

If comparefn is […] not a consistent comparison function for the elements of this array, the behaviour of sort is implementation-defined.

A function comparefn is a consistent comparison function for a set of values S if all of the requirements below are met for all values a, b, and c (possibly the same value) in the set S: The notation a <CF b means comparefn(a,b) < 0; a =CF b means comparefn(a,b) = 0 (of either sign); and a >CF b means comparefn(a,b) > 0.

Calling comparefn(a,b) always returns the same value v when given a specific pair of values a and b as its two arguments. Furthermore, Type(v) is Number, and v is not NaN. Note that this implies that exactly one of a <CF b, a =CF b, and a >CF b will be true for a given pair of a and b.

  • Calling comparefn(a,b) does not modify the this object.
  • a =CF a (reflexivity)
  • If a =CF b, then b =CF a (symmetry)
  • If a =CF b and b =CF c, then a =CF c (transitivity of =CF)
  • If a <CF b and b <CF c, then a <CF c (transitivity of <CF)
  • If a >CF b and b >CF c, then a >CF c (transitivity of >CF)

NOTE: The above conditions are necessary and sufficient to ensure that comparefn divides the set S into equivalence classes and that these equivalence classes are totally ordered.

An optimised sort function can easily screw up if these properties are not fulfilled. That can mean no sorting at all as well as never terminating sorting. See Is it correct to use JavaScript Array.sort() method for shuffling?

Instead, use a standard shuffle algorithm, there are many good ones. The Fisher-Yates-Shuffle is really simple to implement for example, see the question How to randomize (shuffle) a JavaScript array?.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for explaining it in detail and linking that question. I guess, Fisher-Yates-Shuffle is the ideal way to go. I will try to learn more about that and implement the same. – Janaaaa Dec 21 '12 at 06:43
1

The problem is the input in this case.

parseInt( Math.random()*10 ) returns an even number all the time, thus taking your randomising function for a toss.

Something similar I found in this link. Hope it also helps.

In Safari and I think also IE7 and 8 Math.random() is NOT random?

Community
  • 1
  • 1
LPD
  • 2,833
  • 2
  • 29
  • 48
  • Thanks for linking that question. I am able to get it work using the code suggested there: Math.floor(Math.random() * 3) - 1; That was quick and easy!! – Janaaaa Dec 21 '12 at 06:40
  • `parseInt( Math.random()*10 )` returns uneven numbers as well as even numbers. Why don't you think that? – Bergi Dec 21 '12 at 06:46
  • Any number * 10 should always be even mate. – LPD Dec 21 '12 at 06:50
  • @LPD: Not if it is a decimal number :) – Janaaaa Dec 21 '12 at 07:01
  • I just checked in jsFiddle, Math.random gives you decimal numbers. Something like 0.7345683857680161. And when we multiply it by 10 and parseInt, we end up with the first digit which is 7 in this case. – Janaaaa Dec 21 '12 at 07:02
1

The problem is the shuffling method. An easy but still wrong way to fix it is change the whole sort function to return Math.random() < 0.5 ? 1 : -1;. But that isn't guaranteed to randomize the list properly (an element will move up with a probability of 50%, whereas in a true shuffle it will move up with a probability proportional to the number of elements originally above it).

The correct thing to do is implement a Fisher-Yates shuffle. This is also O(n) rather than O(n log n).

ebsddd
  • 1,028
  • 9
  • 20