1

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?

Vahe
  • 1,699
  • 3
  • 25
  • 76
  • 1
    [`sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description) expects either a negative or positive value to determine ordering on each iteration. – pilchard Sep 19 '21 at 09:44
  • It looks like my question really went beyond the scope of the core problem which was how sort works, thank you for clarifying, in the case of this question I'd like to keep the question active, because clearly I did not understand why the positive or negative value was important. Is the sign and magnitude both used to determine order and Left Right placement with respect to a value of comparison? – Vahe Sep 19 '21 at 09:47
  • If sign is important, then magnitude is likely not, is that more accurate? Since a negative number has equal likelihood to be selected with a positive number then all I see that is needed is the sign of this number to determine what side (Left/Right) to insert the presently examined array value for shuffle – Vahe Sep 19 '21 at 09:49
  • 2
    Only the sign is significant as `sort()` works iteratively over succesive pairs of elements. – pilchard Sep 19 '21 at 09:49
  • Ok, so magnitude of value only determines the uniformity of this negative positive value value generated so that they have equal likelihood to occur. that was why I got confused to begin – Vahe Sep 19 '21 at 09:51
  • It should be noted that this is a strongly biased 'shuffle' see: [How to randomize (shuffle) a JavaScript array?](https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array) for more discussion – pilchard Sep 19 '21 at 09:53
  • Since positive numbers are all sign of positive, then the each element produced will always occur to the positive or right side of the previous element, I hope my description covered and removed my vagueness in the question as probability was not a topic I favored when first learning it. – Vahe Sep 19 '21 at 09:54
  • Is that considered strongly biased because it starts from the first element which is the bias point? If so, that clarifies and makes sense the definition. – Vahe Sep 19 '21 at 09:55
  • Can this question or title be revised in a way that I can generally provide benefit for another reader, and possibly not impact my point reputation? To the downvoter, does It include too broad of a background, or a non targeted question? Please suggest. – Vahe Sep 19 '21 at 09:59
  • 1
    i'm not going to leave a downvote, but man you could say this all so much simpler. i imagine that's why the other person downvoted. you seem to have the right intentions though. cheers – Aaron Plocharczyk Sep 19 '21 at 16:15
  • Aaron, let me remove the fluff, I have so much overlapping stuff in my head I should be able to simplify this to be comprehendable – Vahe Sep 19 '21 at 16:47
  • 1
    The question has been surgically corrected – Vahe Sep 19 '21 at 16:54
  • This looks to me as an attempt to shuffle the array by using the [decorate-sort-undecorate paradigm](https://en.wikipedia.org/wiki/Schwartzian_transform), that is sorting the array after assigning random “values” to its elements. Unfortunately, this is not how the JavaScript sort() method is supposed to work. You would need some wrapper around it. – jpmarinier Sep 19 '21 at 18:59
  • @jpmariner, please elaborate and why it does not produce a somewhat biased shuffled array, can you elaborate as a formal answer, for upvote consideration – Vahe Sep 19 '21 at 19:01
  • @jpmariner, please clarify why sorting does not work with negative positive values, as previously answered, if it is completely wrong, what wrapper is needed in this case? Does the sort need to utilize relative direction, and magnitude of the positive, negative values, or just the sign component, or something completely different? – Vahe Sep 19 '21 at 19:13

1 Answers1

3

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>
Aaron Plocharczyk
  • 2,776
  • 2
  • 7
  • 15
  • 1
    No worries! Thanks for simplifying the question. It helped me better understand what you needed to know. I'm gonna give you an upvote to bring you back to at least even points on this. You brought up an interesting and often ignored corner case for JavaScript's sort function. – Aaron Plocharczyk Sep 21 '21 at 03:40
  • Most glad to be of help as you have helped me revise the question and simultaneously solve the details of this problem. – Vahe Sep 21 '21 at 04:19