Is it correct to use JavaScript Array.sort() method for shuffling?

前端 未结 12 1622
广开言路
广开言路 2020-11-22 03:48

I was helping somebody out with his JavaScript code and my eyes were caught by a section that looked like that:

function randOrd(){
  return (Math.round(Math         


        
相关标签:
12条回答
  • 2020-11-22 04:31

    If you're using D3 there is a built-in shuffle function (using Fisher-Yates):

    var days = ['Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi','Dimanche'];
    d3.shuffle(days);
    

    And here is Mike going into details about it:

    http://bost.ocks.org/mike/shuffle/

    0 讨论(0)
  • 2020-11-22 04:32

    There is nothing wrong with it.

    The function you pass to .sort() usually looks something like

    function sortingFunc( first, second )
    {
      // example:
      return first - second ;
    }
    

    Your job in sortingFunc is to return:

    • a negative number if first goes before second
    • a positive number if first should go after second
    • and 0 if they are completely equal

    The above sorting function puts things in order.

    If you return -'s and +'s randomly as what you have, you get a random ordering.

    Like in MySQL:

    SELECT * from table ORDER BY rand()
    
    0 讨论(0)
  • 2020-11-22 04:37

    It's been four years, but I'd like to point out that the random comparator method won't be correctly distributed, no matter what sorting algorithm you use.

    Proof:

    1. For an array of n elements, there are exactly n! permutations (i.e. possible shuffles).
    2. Every comparison during a shuffle is a choice between two sets of permutations. For a random comparator, there is a 1/2 chance of choosing each set.
    3. Thus, for each permutation p, the chance of ending up with permutation p is a fraction with denominator 2^k (for some k), because it is a sum of such fractions (e.g. 1/8 + 1/16 = 3/16).
    4. For n = 3, there are six equally-likely permutations. The chance of each permutation, then, is 1/6. 1/6 can't be expressed as a fraction with a power of 2 as its denominator.
    5. Therefore, the coin flip sort will never result in a fair distribution of shuffles.

    The only sizes that could possibly be correctly distributed are n=0,1,2.


    As an exercise, try drawing out the decision tree of different sort algorithms for n=3.


    There is a gap in the proof: If a sort algorithm depends on the consistency of the comparator, and has unbounded runtime with an inconsistent comparator, it can have an infinite sum of probabilities, which is allowed to add up to 1/6 even if every denominator in the sum is a power of 2. Try to find one.

    Also, if a comparator has a fixed chance of giving either answer (e.g. (Math.random() < P)*2 - 1, for constant P), the above proof holds. If the comparator instead changes its odds based on previous answers, it may be possible to generate fair results. Finding such a comparator for a given sorting algorithm could be a research paper.

    0 讨论(0)
  • 2020-11-22 04:38

    Here's an approach that uses a single array:

    The basic logic is:

  • Starting with an array of n elements
  • Remove a random element from the array and push it onto the array
  • Remove a random element from the first n - 1 elements of the array and push it onto the array
  • Remove a random element from the first n - 2 elements of the array and push it onto the array
  • ...
  • Remove the first element of the array and push it onto the array
  • Code:

    for(i=a.length;i--;) a.push(a.splice(Math.floor(Math.random() * (i + 1)),1)[0]);
    
0 讨论(0)
  • 2020-11-22 04:41

    I have placed a simple test page on my website showing the bias of your current browser versus other popular browsers using different methods to shuffle. It shows the terrible bias of just using Math.random()-0.5, another 'random' shuffle that isn't biased, and the Fisher-Yates method mentioned above.

    You can see that on some browsers there is as high as a 50% chance that certain elements will not change place at all during the 'shuffle'!

    Note: you can make the implementation of the Fisher-Yates shuffle by @Christoph slightly faster for Safari by changing the code to:

    function shuffle(array) {
      for (var tmp, cur, top=array.length; top--;){
        cur = (Math.random() * (top + 1)) << 0;
        tmp = array[cur]; array[cur] = array[top]; array[top] = tmp;
      }
      return array;
    }
    

    Test results: http://jsperf.com/optimized-fisher-yates

    0 讨论(0)
  • 2020-11-22 04:42

    Interestingly, Microsoft used the same technique in their pick-random-browser-page.

    They used a slightly different comparison function:

    function RandomSort(a,b) {
        return (0.5 - Math.random());
    }
    

    Looks almost the same to me, but it turned out to be not so random...

    So I made some testruns again with the same methodology used in the linked article, and indeed - turned out that the random-sorting-method produced flawed results. New test code here:

    function shuffle(arr) {
      arr.sort(function(a,b) {
        return (0.5 - Math.random());
      });
    }
    
    function shuffle2(arr) {
      arr.sort(function(a,b) {
        return (Math.round(Math.random())-0.5);
      });
    }
    
    function shuffle3(array) {
      var tmp, current, top = array.length;
    
      if(top) while(--top) {
        current = Math.floor(Math.random() * (top + 1));
        tmp = array[current];
        array[current] = array[top];
        array[top] = tmp;
      }
    
      return array;
    }
    
    var counts = [
      [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]
    ];
    
    var arr;
    for (var i=0; i<100000; i++) {
      arr = [0,1,2,3,4];
      shuffle3(arr);
      arr.forEach(function(x, i){ counts[x][i]++;});
    }
    
    alert(counts.map(function(a){return a.join(", ");}).join("\n"));
    
    0 讨论(0)
  • 提交回复
    热议问题