18

I want to randomly shuffle a list of 4 items but with a seed so that so long as you have the same seed the you will get the same order of items.

["a", "b", "c", "d"]

I figure I can get the seed with Math.random, I don't need something very exact. How do I sort according to the seed?

Harry
  • 52,711
  • 71
  • 177
  • 261
  • 1
    You don't want to *sort*, you want to *shuffle*. When you do it, you'll use random numbers during the process, so just establish the seed before you begin. – Pointy May 28 '13 at 21:21
  • 2
    ... oh wait; you want to know whether there's something like `Math.seed()` (made up) ... well [here is a related question](http://stackoverflow.com/questions/521295/javascript-random-seeds) – Pointy May 28 '13 at 21:22
  • @Antony isn't that question about getting a seed. I can get a seed easily I think, I need a way to shuffle based on the seed. – Harry May 28 '13 at 21:33
  • 3
    There are only 24 possible permutations, so you could store them in an array and just use `permutations[seed]`, where seed is a number between 0 and 23 – georg May 28 '13 at 21:36
  • 1
    @Harry well the comment from thg435 is a great idea if you really only have 4 elements to worry about. If you might have a larger number of elements, then you'd do a [shuffle](http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) with a seedable random number routine. – Pointy May 28 '13 at 21:44
  • 2
    @Pointy is correct. The shuffle itself isn't that much of a problem (just use Fisher-Yates), but you need a *seedable* random generator instead of the default randomly-seeded `Math.random()`. – Mattias Buelens May 28 '13 at 21:50

4 Answers4

20

You can achieve this with a slight modification to Mike Bostock's implementation of the Fisher–Yates algorithm*:

function shuffle(array, seed) {                // <-- ADDED ARGUMENT
  var m = array.length, t, i;

  // While there remain elements to shuffle…
  while (m) {

    // Pick a remaining element…
    i = Math.floor(random(seed) * m--);        // <-- MODIFIED LINE

    // And swap it with the current element.
    t = array[m];
    array[m] = array[i];
    array[i] = t;
    ++seed                                     // <-- ADDED LINE
  }

  return array;
}

function random(seed) {
  var x = Math.sin(seed++) * 10000; 
  return x - Math.floor(x);
}

*The random function is taken from this SO answer. It is a hack and not entirely random and most importantly not cryptographically secure! Here's a histogram of samples (also in the comments of that response, takes a while to run). Conclusively, you should only use this when these things don't really matter. Alternatively, substitute the random function with a better seedable random number generator.

Ulf Aslak
  • 7,876
  • 4
  • 34
  • 56
  • Very cool—thank you! https://stackoverflow.com/a/521323/470749 led me to this version of `random` using David Bau's seedrandom library: `function random(seed) { const rng = seedrandom(seed); const randomNum = rng(); return randomNum; }` – Ryan May 16 '20 at 13:37
  • 2
    FYI, the function returns an array, but that's not necessary. The input array itself is altered. If you still need to access the old array order, then you'll need to make a copy first, or something. – user984003 Mar 10 '21 at 13:16
  • I actually think it's misleading to have a return statement in a function that mutates its arguments. I spent 30 minutes tricked by that return statement before realizing that the function does alter the original input… – Sacha May 31 '23 at 03:01
3

Shuffling an array with a seed is two separate problems:

  1. Seeding the random number generator used to shuffle the array
  2. Using the seeded random number generator to shuffle the array

For seeding the random number generator, see this fantastic answer in the thread Seeding the random number generator in Javascript.

For shuffling an array, see the Fisher-Yates shuffle in How can I shuffle an array?.

Here's some code that conveniently wraps the two together. It's up to you to copy and paste a couple of small functions from this answer. I'd rather not dupe the code in case it changes, and this lets you plug and play whatever seeded random functions you want.

// TODO: copy and paste mulberry32 and cyrb128 from
//       https://stackoverflow.com/a/47593316/6243352

const seededRandom = ({rng = null, seed = "apples"} = {}) => {
  rng = rng || mulberry32(cyrb128(seed)[0]);

  const rnd = (lo, hi, defaultHi=1) => {
    if (hi === undefined) {
      hi = lo === undefined ? defaultHi : lo;
      lo = 0;
    }

    return rng() * (hi - lo) + lo;
  };

  const rndInt = (lo, hi) => Math.floor(rnd(lo, hi, 2));

  const shuffle = a => {
    for (let i = a.length - 1; i > 0; i--) {
      const j = rndInt(i + 1);
      const x = a[i];
      a[i] = a[j];
      a[j] = x;
    }
  };

  return {rnd, rndInt, shuffle};
};

module.exports = seededRandom;

You can use it like:

const seededRandom = require("./seeded-random");

const {
  rnd, rndInt, shuffle
} = seededRandom({seed: "optional seed string"});
const a = [...Array(5)].map((_, i) => i);
shuffle(a);

// comments assume mulberry32 and cyrb128 from
// https://stackoverflow.com/a/47593316/6243352
console.log(a); // => always [ 4, 3, 0, 1, 2 ]
console.log(rnd()); // => always 0.018376975087448955
console.log(rndInt(42)); // => always 30
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Looks like the xmur3 function is no longer in that answer. Do you happen to have it? You may also want to post mulberry32 in case that is removed, and put a TODO to instead make sure that the functions haven't changed in that answer. – srchulo Sep 07 '22 at 03:46
  • Found xmur3 here, not sure if it's correct: https://gist.github.com/tionkje/6ab66360dcfe9a9e2b5560742d259389 – srchulo Sep 07 '22 at 03:47
  • Thanks for the note. If it was removed, there was probably a good reason for it. You can always get `xmur3` from the [edit history](https://stackoverflow.com/posts/47593316/revisions) for that question, which is permanent. The edit message says "Improve, replace xmur3 with xmur128", although it looks like the function it was replaced with was called `cyrb128` in the code so I'll reference that here. – ggorlen Sep 07 '22 at 04:24
  • There's also [this gist](https://github.com/bryc/code/blob/master/jshash/PRNGs.md) from bryc, the author of the linked answer. It still has `xmur3` but not `cyrb128` yet. The code changes slightly since `cyrb128` returns an array rather than a function as in `xmur3`, so `cyrb128(seed)[0]` rather than `xmur3(seed)()`. Check the [edit history](https://stackoverflow.com/posts/68523152/revisions) of this question if you want to use `xmur3`. – ggorlen Sep 07 '22 at 04:44
1

You can create random numbers to do the sorting using the XOR Shift method. Example. Then just replace Math.random() in your old code with new Xor128(seed).make(3)[2] / 4294967296 * 2

DankMemes
  • 2,085
  • 2
  • 21
  • 30
-1

jsFiddle Demo

You would need to seed a random value for each value in the array as far as I can tell. In that regards, you would probably want to do something like this:

for( var i = 0; i < length; i++ ){
    seed.push(Math.random());
}

Where you are insuring that length is the same length that the seed is for. This would be 4 for your simple example. Once that was done, you could pass the seed into your shuffle (or sort) function to ensure that the same results were obtained. The shuffle would need to use it in a loop as well

    var randomIndex = parseInt(seed[i] * (len - i));

So here is what it all breaks down into

The seed function which will store the array of seeds

var seeder = function(){
 var seed = [];
 return {
  set:function(length){
    for( var i = 0; i < length; i++ ){
        seed.push(Math.random());
    }
    return seed;
  },
  get: function(){
   return seed;
  },
  clear: function(){
   seed = []; 
  }
 };
}

A pretty basic shuffle

function randomShuffle(ar,seed){
var numbers = [];
for( var a = 0, max = ar.length; a < max; a++){
    numbers.push(a);
}
var shuffled = [];
for( var i = 0, len = ar.length; i < len; i++ ){
    var r = parseInt(seed[i] * (len - i));
    shuffled.push(ar[numbers[r]]);
    numbers.splice(r,1);
}
return shuffled;
}

Being used

var arr = ["a", "b", "c", "d"];
var seed = seeder();
seed.set(arr.length);
console.log(randomShuffle(arr,seed.get()));
console.log(randomShuffle(arr,seed.get()));
console.log(randomShuffle(arr,seed.get()));
Travis J
  • 81,153
  • 41
  • 202
  • 273