62

In particular, I want to make sure to avoid the mistake made in Microsoft's Browser Choice shuffle code. That is, I want to make sure that each letter has an equal probability of ending up in each possible position.

e.g. Given "ABCDEFG", return something like "GEFBDCA".

Liam
  • 19,819
  • 24
  • 83
  • 123

16 Answers16

102

I modified an example from the Fisher-Yates Shuffle entry on Wikipedia to shuffle strings:

String.prototype.shuffle = function () {
    var a = this.split(""),
        n = a.length;

    for(var i = n - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
    return a.join("");
}
console.log("the quick brown fox jumps over the lazy dog".shuffle());
//-> "veolrm  hth  ke opynug tusbxq ocrad ofeizwj"

console.log("the quick brown fox jumps over the lazy dog".shuffle());
//-> "o dt hutpe u iqrxj  yaenbwoolhsvmkcger ozf "

More information can be found in Jon Skeet's answer to Is it correct to use JavaScript Array.sort() method for shuffling?.

Community
  • 1
  • 1
Andy E
  • 338,112
  • 86
  • 474
  • 445
  • Thanks, this is definitely more uniform than some other examples I found. – Liam Oct 15 '10 at 16:23
  • Note [this answer](https://stackoverflow.com/a/6274381/6575658) which explains how in ES6 you can swap elements with `[a[i], a[j]] = [a[j], a[i]]`. – knueser Apr 25 '20 at 19:35
63

If "truly" randomness is important, I recommend against this. See my below edit.

I just wanted to add my favorite method for a little variety ;)

Given a string:

var str = "My bologna has a first name, it's O S C A R.";

Shuffle in one line:

var shuffled = str.split('').sort(function(){return 0.5-Math.random()}).join('');

Outputs:

oa, a si'rSRn f gbomi. aylt AtCnhO ass eM
as'oh ngS li Ays.rC nRamsb Oo ait a ,eMtf
y alCOSf e gAointsorasmn bR Ms .' ta ih,a

EDIT: As @PleaseStand has pointed out, this doesn't meet OP's question at all since it does suffer from "Microsoft's Browser Choice shuffle" code. This isn't a very good randomizer if your string needs to be close to random. It is however, awesome at quickly "jumbling" your strings, where "true" randomness is irrelevant.

The article he links below is a great read, but explains a completely different use case, which affects statistical data. I personally can't imagine a practical issue with using this "random" function on a string but as a coder, you're responsible for knowing when not to use this.

I've left this here for all the casual randomizers out there.

Joel Mellon
  • 3,672
  • 1
  • 26
  • 25
  • -1: This has the exact "mistake made in Microsoft's Browser Choice shuffle code." http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html – PleaseStand Aug 27 '14 at 23:40
  • 2
    @PleaseStand You're absolutely correct. I've always known this to be a pretty lazy hack that doesn't actually produce very random results. I'll update my answer, although I wouldn't say it's the "exact mistake" - that was for survey results. – Joel Mellon Aug 28 '14 at 17:42
14

Shortest One Liner:

const shuffle = str => [...str].sort(()=>Math.random()-.5).join('');

Does not guarantee statistically equal distribution but usable in most cases for me.

const shuffle = str => [...str].sort(()=>Math.random()-.5).join('');
document.write(shuffle("The quick brown fox jumps over the lazy dog"));
chickens
  • 19,976
  • 6
  • 58
  • 55
8

Even though this has been answered, I wanted to share the solution I came up with:

function shuffleWord (word){
    var shuffledWord = '';
    word = word.split('');
    while (word.length > 0) {
      shuffledWord +=  word.splice(word.length * Math.random() << 0, 1);
    }
    return shuffledWord;
}

// 'Batman' => 'aBmnta'

You can also try it out (jsfiddle).

Snippet is 100k shuffles of 3 character string:

var arr={}, cnt=0, loops=100000, ret;
do{
  ret=shuffle('abc');
  arr[ret]=(ret in arr?arr[ret]+1:1);
}while(++cnt<loops);  

for(ret in arr){
  document.body.innerHTML+=ret+' '+arr[ret]+' '+(100*arr[ret]/loops).toFixed(1)+'%<br>';
}

function shuffle(wd){var t="";for(wd=wd.split("");wd.length>0;)t+=wd.splice(wd.length*Math.random()<<0,1);return t}
ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Maximilian Lindsey
  • 809
  • 4
  • 18
  • 35
  • Tested randomness; works well. Here's a compressed version: `function shuffle(wd){var t="";for(wd=wd.split("");wd.length>0;)t+=wd.splice(wd.length*Math.random()<<0,1);return t}` – ashleedawg Mar 31 '22 at 15:58
2

Using Fisher-Yates shuffle algorithm and ES6:

// Original string
let string = 'ABCDEFG';

// Create a copy of the original string to be randomized ['A', 'B', ... , 'G']
let shuffle = [...string];

// Defining function returning random value from i to N
const getRandomValue = (i, N) => Math.floor(Math.random() * (N - i) + i);

// Shuffle a pair of two elements at random position j (Fisher-Yates)
shuffle.forEach( (elem, i, arr, j = getRandomValue(i, arr.length)) => [arr[i], arr[j]] = [arr[j], arr[i]] );

// Transforming array to string
shuffle = shuffle.join('');

console.log(shuffle);
// 'GBEADFC'
Erik Martín Jordán
  • 4,332
  • 3
  • 26
  • 36
1

Single line solution and limited output length...

var rnd = "ABCDEF23456789".split('').sort(function(){return 0.5-Math.random()}).join('').substring(0,6);
MCunha98
  • 81
  • 3
  • 12
1

The following one-liners are unbiased:

Fisher–Yates - Take from one list and put on another or itself (Durstenfeld/Knuth):

"ABCDEFG".split("").map((v, i, a) => (r => ([v, a[r]] = [a[r], v], v))(Math.floor(Math.random() * (a.length - i)) + i)).join("")

Schwartzian transform - Attach random values to each element and sort by them:

"ABCDEFG".split("").map(v => [v, Math.random()]).sort((a, b) => a[1] - b[1]).map(v => v[0]).join("")
0
String.prototype.shuffle=function(){

   var that=this.split("");
   var len = that.length,t,i
   while(len){
    i=Math.random()*len-- |0;
    t=that[len],that[len]=that[i],that[i]=t;
   }
   return that.join("");
}
0
                  shuffleString = function(strInput){
                     var inpArr = strInput.split("");//this will give array of input string
                     var arrRand = []; //this will give shuffled array
                     var arrTempInd = []; // to store shuffled indexes
                     var max = inpArr.length;
                     var min = 0;
                     var tempInd;
                     var i =0 ;

                      do{
                           tempInd = Math.floor(Math.random() * (max - min));//to generate random index between range
                           if(arrTempInd.indexOf(tempInd)<0){ //to check if index is already available in array to avoid repeatation
                                arrRand[i] = inpArr[tempInd]; // to push character at random index
                                arrTempInd.push(tempInd); //to push random indexes 
                                i++;
                            }
                       }
                        while(arrTempInd.length < max){ // to check if random array lenght is equal to input string lenght
                            return arrRand.join("").toString(); // this will return shuffled string
                        }
                 };

Just pass the string to function and in return get the shuffled string

Mayur Nandane
  • 309
  • 2
  • 8
0

A different take on scrambling a word. All other answers with enough iterations will return the word unscrambled, mine does not.

var scramble = word => {

    var unique = {};
    var newWord = "";
    var wordLength = word.length;

    word = word.toLowerCase(); //Because why would we want to make it easy for them?

    while(wordLength != newWord.length) {

        var random = ~~(Math.random() * wordLength);

        if(

          unique[random]
          ||
          random == newWord.length && random != (wordLength - 1) //Don't put the character at the same index it was, nore get stuck in a infinite loop.

        ) continue; //This is like return but for while loops to start over.

        unique[random] = true;
        newWord += word[random];

    };

    return newWord;

};

scramble("God"); //dgo, gdo, ogd
Darkrum
  • 1,325
  • 1
  • 10
  • 27
0

Yet another Fisher-Yates implementation:

const str = 'ABCDEFG',

      shuffle = str => 
        [...str]
          .reduceRight((res,_,__,arr) => (
            res.push(...arr.splice(0|Math.random()*arr.length,1)),
            res) ,[])
          .join('')

console.log(shuffle(str))
.as-console-wrapper{min-height:100%;}
Yevhen Horbunkov
  • 14,965
  • 3
  • 20
  • 42
0

Just for the sake of completeness even though this may not be exactly what the OP was after as that particular question has already been answered.

Here's one that shuffles words.

Here's the regex explanation for that: https://regex101.com/r/aFcEtk/1

And it also has some funny outcomes.

// Shuffles words
// var str = "1 2 3 4 5 6 7 8 9 10";
var str = "the quick brown fox jumps over the lazy dog A.S.A.P. That's right, this happened.";
var every_word_im_shuffling = str.split(/\s\b(?!\s)/).sort(function(){return 0.5-Math.random()}).join(' ');
console.log(every_word_im_shuffling);
Ste
  • 1,729
  • 1
  • 17
  • 27
0

Rando.js uses a cryptographically secure version of the Fisher-Yates shuffle and is pretty short and readable.

console.log(randoSequence("This string will be shuffled.").join(""));
<script src="https://randojs.com/2.0.0.js"></script>
Aaron Plocharczyk
  • 2,776
  • 2
  • 7
  • 15
0

You can't shuffe a string in place but you can use this:

String.prototype.shuffle = function() {
    var len = this.length;
    var d = len;
    var s = this.split("");
    var array = [];
    var k, i;
    for (i = 0; i < d; i++) {
        k = Math.floor(Math.random() * len);
        array.push(s[k]);
        s.splice(k, 1);
        len = s.length;
    }
    for (i = 0; i < d; i++) {
        this[i] = array[i];
    }
    return array.join("");
}

var s = "ABCDE";
s = s.shuffle();
console.log(s);
Blackjack
  • 1,322
  • 1
  • 16
  • 21
  • 1
    Please don't mutate native type's prototype. Doing so can lead to all sorts of weird and hard to debug behaviors. – Mat Jun 22 '22 at 06:35
0

If I had a bunch of letters on a table (as in scrabble for example), this is how I would shuffle them. This function mimics that process. I would simply select a letter randomly, one letter at a time, remove it from its current location, and put it in a new location.

I didn't see the need to use anything other than a while loop and basic string operations.

function stringScrambler(theString) {
    let scrambledString = "";
    while (theString.length > 0) {

        //pick a random character from the string 
        let characterIndex = Math.floor(Math.random() * (theString.length));
        let theCharacter = theString[characterIndex];

        //add that character to the new string
        scrambledString += theCharacter;

        //remove that character from the original string
        theString = theString.slice(0, characterIndex) + theString.slice(characterIndex + 1, theString.length);
    }

    return scrambledString;
}
garydavenport73
  • 379
  • 2
  • 8
-1
String.prototype.shuffle = function(){
  return this.split('').sort(function(a,b){
    return (7 - (Math.random()+'')[5]);
  }).join('');
};
Captain Fail
  • 77
  • 1
  • 3
  • 1
    Answering a 7 year old question with unexplained code? – traktor Nov 06 '17 at 22:57
  • 3
    Is this just another way to write [this answer](https://stackoverflow.com/a/25419830/2350083)? – Jon Nov 06 '17 at 23:12
  • 1
    Nearly every answer doesn't explain what it does. It's up to the op or other people to learn or ask if they have a query. Plus this 7-year-old question is still relevant. If I asked this today you'd probably mark it as a duplicate linking to this very question. – Ste Nov 11 '19 at 23:19