davidbau / seedrandom

seeded random number generator for Javascript
2.04k stars 160 forks source link

Why doesn't this work with lodash? #32

Closed Lomacar closed 8 years ago

Lomacar commented 8 years ago

If I do a simple test like

Math.seedrandom(1987598823)
console.log(_.random(999))

It ends up producing different numbers every time. Whereas if I use underscore or just Math.random it produces a consistent number, as expected. Lodash simply says nativeRandom = Math.random; and then uses nativeRandom throughout for its randomness. I call Math.seedrandom before loading lodash and it still produces inconsistent results.

tmedwards commented 8 years ago

The line nativeRandom = Math.random; takes a reference to whichever PRNG lives at Math.random at the time when the reference is taken. If you load Lodash before you replace the JavaScript native Math.random(), then its reference will be to the native—even if you later replace what Math.random refers to. That said, creating a fresh Lodash context should fix the issue, as the new context will grab a new reference to Math.random()—YMMV.

AFAIK, replacing Math.random before loading Lodash should get you the results you seem to want. I'm unsure why doing so didn't seem to work for you. Have you tried creating a new context (see: _.runInContext())?

Underscore works as expected because it never takes a reference to Math.random, instead calling it directly, so replacing it later replaces the PRNG it calls.

Regardless, this isn't something that seedrandom has any control over.

Lomacar commented 8 years ago

So doing

lodash = _.runInContext();
Math.seedrandom("hey"); 
lodash.random(999)

should produce consistent results? Because it doesn't.

I figured I have a better chance asking here about using lodash than asking somewhere else about using seedrandom.

tmedwards commented 8 years ago

No. You're doing it backwards again. When invoked, Lodash's runInContext method captures a reference to whichever PRNG is currently referred to by Math.random. So, creating the new Lodash context before replacing Math.random still captures a reference to JavaScript's native PRNG.

To have Lodash capture seedrandom's PRNG, you need to initially load/require Lodash after altering Math.random. If that's not feasible with your setup, or working for you as you claim, then you'll need to replace the current Lodash context, which captured the native PRNG, with a new context after you replace Math.random.

For example, and without altering the default Lodash instance (_):

/* Alter Math.random to refer to seedrandom's PRNG. */
Math.seedrandom("hey");

/* Assign a new Lodash context to a separate variable AFTER altering Math.random. */
var lodash = _.runInContext();

/* Should use seedrandom's PRNG. */
lodash.random(999);

AFAIK, you should even be able to replace the default Lodash instance, if you'd prefer. For example:

/* Alter Math.random to refer to seedrandom's PRNG. */
Math.seedrandom("hey");

/* Assign a new Lodash context to _ AFTER altering Math.random. */
_ = _.runInContext();

/* Should use seedrandom's PRNG. */
_.random(999);
Lomacar commented 8 years ago

Ah, now I get it. Thanks, that does work.

oprogramador commented 6 years ago

@Lomacar @tmedwards I discovered another workaround for lodash; your solution requires writing _ = _.runInContext(); after each Math.seedrandom but I have another which requires changes only in one place

const _ = require('lodash');
require('seedrandom');

function sample(array) {
  const length = array == null ? 0 : array.length
  return length ? array[Math.floor(Math.random() * length)] : undefined
}

_.sample = sample;
// also copy other lodash methods which you use

Math.seedrandom('foo');
console.log(_.sample(_.range(100))); // always 46

Math.seedrandom('bar');
console.log(_.sample(_.range(100))); // always 37

// seeding lodash methods also works in the required files which only require lodash and call Math.seedrandom