Çekilebilir JavaScript rastgele sayı üreteci


154

JavaScript Math.random()işlevi, geçerli saate göre otomatik olarak tohumlanan 0 ile 1 arasında rastgele bir değer döndürür (inandığım Java'ya benzer). Ancak, bunun için kendi tohumunuzu oluşturmanın bir yolu olduğunu sanmıyorum.

Tekrarlanabilir (sözde) rasgele sayılar dizisi üretmesini sağlamak için kendi çekirdek değerimi sağlayabileceğim bir rasgele sayı üretecini nasıl yapabilirim?


1
Not: Bu soruyu kısa ve odaklanmış tutmak adına, yukarıdaki soruda bulunan kodu aşağıdaki Topluluk Wiki yanıtına böldüm.
Ilmari Karonen

Yanıtlar:


127

Bir seçenek, güzel özelliklere sahip tohumlanabilir RC4 tabanlı Math.random () drop-in yerine http://davidbau.com/seedrandom .


21
David Bau'nun seedrandom'u o zamandan beri yeterince popüler hale geldi ve onu burada github'da korudu . ECMAScript'in o kadar uzun zamandır o kadar geri planda kalması çok yazık ki böyle şeyler dile dahil edilmiyor. Cidden, tohumlama yok !!!
yiyin

2
@EatatJoes, Bunun hem gerekli hem de mümkün olması JS'nin hem utanç hem de ihtişamı. Bir dosya ekleyip Math nesnesinde geriye dönük olarak uyumlu değişiklikler yapabilmeniz oldukça güzel. 10 günlük iş için fena değil, Brendan Eich.
Bruno Bronosky

3
Bu proje için npm sayfasını arayanlar için: npmjs.com/package/seedrandom
Kip

25

Tohumlama yeteneğine ihtiyacınız yoksa, sadece Math.random()etrafında yardımcı fonksiyonlar kullanın ve oluşturun (örn. randRange(start, end)).

Hangi RNG'yi kullandığınızdan emin değilim, ancak özelliklerini ve sınırlamalarını bilmeniz için onu bilmek ve belgelemek en iyisidir.

Starkii'nin dediği gibi, Mersenne Twister iyi bir PRNG'dir, ancak uygulaması kolay değildir. Kendiniz yapmak istiyorsanız, bir LCG uygulamayı deneyin - çok kolaydır, iyi rastgelelik niteliklerine sahiptir (Mersenne Twister kadar iyi değildir) ve bazı popüler sabitleri kullanabilirsiniz.

DÜZENLEME: LCG seçeneği de dahil olmak üzere kısa başlatılabilir RNG uygulamaları için bu yanıtta harika seçenekleri değerlendirin .

function RNG(seed) {
  // LCG using GCC's constants
  this.m = 0x80000000; // 2**31;
  this.a = 1103515245;
  this.c = 12345;

  this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextInt = function() {
  this.state = (this.a * this.state + this.c) % this.m;
  return this.state;
}
RNG.prototype.nextFloat = function() {
  // returns in range [0,1]
  return this.nextInt() / (this.m - 1);
}
RNG.prototype.nextRange = function(start, end) {
  // returns in range [start, end): including start, excluding end
  // can't modulu nextInt because of weak randomness in lower bits
  var rangeSize = end - start;
  var randomUnder1 = this.nextInt() / this.m;
  return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.choice = function(array) {
  return array[this.nextRange(0, array.length)];
}

var rng = new RNG(20);
for (var i = 0; i < 10; i++)
  console.log(rng.nextRange(10, 50));

var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (var i = 0; i < 10; i++)
  console.log(rng.choice(digits));


2
Modülün 2 ^ 31 olması gerekmez mi? Bu algoritmayı wiki'den okudum .
Trantor Liu

3
Sadece farkında olman için, bu matematiğin dikte ettiği şeyi çıkarmaması anlamında "doğru" değildir. Diğer bir deyişle, bu büyük sayıları kaldırabilecek bir dilin farklı bir sonucu olacaktır. JS, büyük sayılarda boğuluyor ve hassasiyeti kesiyor (sonuçta bunlar yüzerdir).
DDS

5
-1 Bu LCG uygulaması, this.a * this.state2 ^ 53'ten büyük bir sayı ile sonuçlanması muhtemel olduğundan , JavaScript'teki tam tamsayı sınırını bozar . Sonuç, sınırlı bir çıktı aralığı ve bazı tohumlar için muhtemelen çok kısa bir dönemdir. Ayrıca genel molarak, bazı oldukça açık modellerde sonuç için ikinin bir kuvvetini kullanmak , basit bir kesme yerine bir modüllü işlem harcadığınızda yine de asal kullanmamak için bir neden yoktur.
aaaaaaaaaaaa

22

Eğer tohum belirtmek edebilmek istiyorsanız, sadece çağrı değiştirmeniz gerekiyor getSeconds()ve getMinutes(). Bir int'i geçebilir ve saniye değeri için yarısı mod 60'ı, diğer yarısını size diğer kısmı vermek için modulo 60'ı kullanabilirsiniz.

Bununla birlikte, bu yöntem çöp gibi görünüyor. Düzgün rastgele sayı oluşturmak çok zordur. Bununla ilgili bariz sorun, rastgele sayı tohumunun saniye ve dakikalara dayanıyor olmasıdır. Çekirdeği tahmin etmek ve rastgele sayı akışınızı yeniden yaratmak için yalnızca 3600 farklı saniye ve dakika kombinasyonunu denemeniz gerekir. Bu aynı zamanda sadece 3600 farklı olası tohum olduğu anlamına gelir. Bu düzeltilebilir, ancak başından beri bu RNG'den şüpheleniyorum.

Daha iyi bir RNG kullanmak istiyorsanız, Mersenne Twister'ı deneyin . Büyük bir yörüngeye ve mükemmel performansa sahip, iyi test edilmiş ve oldukça sağlam bir RNG'dir.

DÜZENLEME: Gerçekten doğru olmalıyım ve buna Sözde Rastgele Sayı Üreticisi veya PRNG olarak bakmalıyım.

"Rasgele sayılar üretmek için aritmetik yöntemler kullanan herkes günah durumundadır."
                                                                                                                                                          --- John von Neumann


1
Mersenne Twister'ın JS uygulamalarına bir bağlantı: math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
orip

1
@orip 3600 başlangıç ​​durumu için bir kaynağınız var mı? Mersenne Twister, 32 bitlik bir sayı ile tohumlanır, bu nedenle PRNG'nin 4 milyar başlangıç ​​durumuna sahip olması gerekir - yalnızca ilk tohum gerçekten rastgele ise.
Tobias P.

2
@TobiasP. GetSeconds () ve getMinutes (), 60 * 60 == 3600 olası başlangıç ​​durumlarının bir kombinasyonuyla tohumlama önerisine atıfta bulunuyordum. Mersenne Twister'dan bahsetmiyordum.
orip

3
@orip Tamam, net değildi. Mersenne Twister'ı ve bir sonraki cümlede başlangıç ​​durumları hakkında konuşuyordunuz;)
Tobias P.

2
Soru soran kişi, kriptografik olarak hassas herhangi bir uygulama için "uygun" rasgele sayı üretimine ihtiyaç duyduklarından bahsetmez. Yanıtın tamamı doğru olsa da, yalnızca ilk paragraf aslında sorulan soruyla ilgilidir. Belki önerilen çözümün bir kod parçacığını ekleyin.
V. Rubinetti


8

Listelediğiniz kod bir tür Lehmer RNG'ye benziyor . Durum böyleyse, o 2147483647zaman en büyük 32 bitlik işaretli tam sayıdır, 2147483647en büyük 32 bitlik asal sayıdır 48271ve sayıları oluşturmak için kullanılan tam dönem çarpanıdır.

Bu doğru ise, değiştirebilir RandomNumberGeneratorfazladan parametresinde almaya seedve ardından ayarlamak this.seediçin seed; ancak tohumun iyi bir rastgele sayı dağılımıyla sonuçlanacağından emin olmalısınız (Lehmer böyle garip olabilir) - ancak çoğu tohum iyi olacaktır.


7

Aşağıdaki, özel bir tohumla beslenebilen bir PRNG'dir. Çağrıldığında SeedRandom, rastgele bir üretici işlevi döndürülür.SeedRandomDöndürülen rastgele işlevi geçerli zamanla tohumlamak için bağımsız değişken olmadan çağrılabilir veya bu tamsayılarla tohumlamak için bağımsız değişken olarak 1 veya 2 negatif olmayan aralıkla çağrılabilir. Sadece 1 değer ile kayan nokta doğruluğu nedeniyle tohumlama, jeneratörün yalnızca 2 ^ 53 farklı durumdan birine başlatılmasına izin verir.

Döndürülen rastgele oluşturucu işlevi, adlandırılmış 1 tamsayı bağımsız değişkeni alır limit, sınır 1 ila 4294965886 aralığında olmalıdır, işlev 0 ila sınır-1 aralığında bir sayı döndürür.

function SeedRandom(state1,state2){
    var mod1=4294967087
    var mul1=65539
    var mod2=4294965887
    var mul2=65537
    if(typeof state1!="number"){
        state1=+new Date()
    }
    if(typeof state2!="number"){
        state2=state1
    }
    state1=state1%(mod1-1)+1
    state2=state2%(mod2-1)+1
    function random(limit){
        state1=(state1*mul1)%mod1
        state2=(state2*mul2)%mod2
        if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
            return random(limit)
        }
        return (state1+state2)%limit
    }
    return random
}

Örnek kullanım:

var generator1=SeedRandom() //Seed with current time
var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
var generator2=SeedRandom(42) //Seed with a specific seed
var fixedVariable=generator2(7) //First value of this generator will always be
                                //1 because of the specific seed.

Bu jeneratör aşağıdaki özellikleri sergiler:

  • Yaklaşık 2 ^ 64 farklı olası iç duruma sahiptir.
  • Yaklaşık 2 ^ 63'lük bir periyodu vardır, bu, herhangi birinin bir JavaScript programında gerçekçi olarak ihtiyaç duyacağından çok daha fazla.
  • modDeğerlerin asal olması nedeniyle , seçilen sınır ne olursa olsun çıktıda basit bir model yoktur. Bu, oldukça sistematik modeller sergileyen bazı basit PRNG'lerden farklıdır.
  • Sınır ne olursa olsun mükemmel bir dağılım elde etmek için bazı sonuçları göz ardı eder.
  • Nispeten yavaştır, makinemde saniyede 10.000.000 kez çalışır.

2
Bu neden bir model oluşturur? for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
Timothy Kanski

@TimothyKanski Çünkü yanlış kullanıyorsunuz. Uzman değilim, ancak bu, üreteci her yinelemede başlattığınız için, yalnızca çekirdeğe dayalı olarak ilk değerini görmeniz ve oluşturucunun sonraki sayıları üzerinde yineleme yapmamanız nedeniyle oluşur. Bunun, belirtilen aralıkta tohumu hash etmeyen herhangi bir PRNG'de olacağına inanıyorum.
bryc

5

Typescript'te programlıyorsanız, Christoph Henkelmann'ın bu konuya verdiği cevaba getirilen Mersenne Twister uygulamasını bir typcript sınıfı olarak uyarladım:

/**
 * copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
 * all rights reserved to him.
 */
export class Random {
    static N = 624;
    static M = 397;
    static MATRIX_A = 0x9908b0df;
    /* constant vector a */
    static UPPER_MASK = 0x80000000;
    /* most significant w-r bits */
    static LOWER_MASK = 0x7fffffff;
    /* least significant r bits */

    mt = new Array(Random.N);
    /* the array for the state vector */
    mti = Random.N + 1;
    /* mti==N+1 means mt[N] is not initialized */

    constructor(seed:number = null) {
        if (seed == null) {
            seed = new Date().getTime();
        }

        this.init_genrand(seed);
    }

    private init_genrand(s:number) {
        this.mt[0] = s >>> 0;
        for (this.mti = 1; this.mti < Random.N; this.mti++) {
            var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
            this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
                + this.mti;
            /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
            /* In the previous versions, MSBs of the seed affect   */
            /* only MSBs of the array mt[].                        */
            /* 2002/01/09 modified by Makoto Matsumoto             */
            this.mt[this.mti] >>>= 0;
            /* for >32 bit machines */
        }
    }

    /**
     * generates a random number on [0,0xffffffff]-interval
     * @private
     */
    private _nextInt32():number {
        var y:number;
        var mag01 = new Array(0x0, Random.MATRIX_A);
        /* mag01[x] = x * MATRIX_A  for x=0,1 */

        if (this.mti >= Random.N) { /* generate N words at one time */
            var kk:number;

            if (this.mti == Random.N + 1)   /* if init_genrand() has not been called, */
                this.init_genrand(5489);
            /* a default initial seed is used */

            for (kk = 0; kk < Random.N - Random.M; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + Random.M] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            for (; kk < Random.N - 1; kk++) {
                y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                this.mt[kk] = this.mt[kk + (Random.M - Random.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
            }
            y = (this.mt[Random.N - 1] & Random.UPPER_MASK) | (this.mt[0] & Random.LOWER_MASK);
            this.mt[Random.N - 1] = this.mt[Random.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];

            this.mti = 0;
        }

        y = this.mt[this.mti++];

        /* Tempering */
        y ^= (y >>> 11);
        y ^= (y << 7) & 0x9d2c5680;
        y ^= (y << 15) & 0xefc60000;
        y ^= (y >>> 18);

        return y >>> 0;
    }

    /**
     * generates an int32 pseudo random number
     * @param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
     * @return {number}
     */
    nextInt32(range:[number, number] = null):number {
        var result = this._nextInt32();
        if (range == null) {
            return result;
        }

        return (result % (range[1] - range[0])) + range[0];
    }

    /**
     * generates a random number on [0,0x7fffffff]-interval
     */
    nextInt31():number {
        return (this._nextInt32() >>> 1);
    }

    /**
     * generates a random number on [0,1]-real-interval
     */
    nextNumber():number {
        return this._nextInt32() * (1.0 / 4294967295.0);
    }

    /**
     * generates a random number on [0,1) with 53-bit resolution
     */
    nextNumber53():number {
        var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
        return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
    }
}

aşağıdaki gibi kullanabilirsiniz:

var random = new Random(132);
random.nextInt32(); //return a pseudo random int32 number
random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
random.nextNumber(); //return a a pseudo random number in range [0,1]

daha fazla yöntem için kaynağı kontrol edin.


0

Not: Bu kod başlangıçta yukarıdaki soruya dahil edildi. Soruyu kısa ve odaklanmış tutmak adına, onu bu Topluluk Wiki yanıtına taşıdım.

Bu kodu tekmeleyerek buldum ve rastgele bir sayı almak ve daha sonra tohumu kullanmak için iyi çalışıyor gibi görünüyor, ancak mantığın nasıl çalıştığından tam olarak emin değilim (örneğin 2345678901, 48271 ve 2147483647 sayılarının nereden geldiği).

function nextRandomNumber(){
  var hi = this.seed / this.Q;
  var lo = this.seed % this.Q;
  var test = this.A * lo - this.R * hi;
  if(test > 0){
    this.seed = test;
  } else {
    this.seed = test + this.M;
  }
  return (this.seed * this.oneOverM);
}

function RandomNumberGenerator(){
  var d = new Date();
  this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
  this.A = 48271;
  this.M = 2147483647;
  this.Q = this.M / this.A;
  this.R = this.M % this.A;
  this.oneOverM = 1.0 / this.M;
  this.next = nextRandomNumber;
  return this;
}

function createRandomNumber(Min, Max){
  var rand = new RandomNumberGenerator();
  return Math.round((Max-Min) * rand.next() + Min);
}

//Thus I can now do:
var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var numbers = ['1','2','3','4','5','6','7','8','9','10'];
var colors = ['red','orange','yellow','green','blue','indigo','violet'];
var first = letters[createRandomNumber(0, letters.length)];
var second = numbers[createRandomNumber(0, numbers.length)];
var third = colors[createRandomNumber(0, colors.length)];

alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");

/*
  If I could pass my own seed into the createRandomNumber(min, max, seed);
  function then I could reproduce a random output later if desired.
*/

3
Vay be, RandomNumberGeneratorve nextRandomNumberişlevleri aslında 1996 yılına kadar uzanıyor. Bir Lehmer / LCG RNG olması gerekiyordu. Aksi takdirde bazı ara değerleri içeremeyecek kadar küçük olabilecek 32 bitlik tam sayılarda modulo aritmetiği gerçekleştirmek için bazı akıllı matematik kullanır. Mesele şu ki, JavaScript 32 bitlik tamsayılar yerine 64 bitlik kayar sayılar uyguluyor ve bölme bu kod gibi tamsayı bölme olmadığından sonucun Lehmer üreteci olmadığını varsayıyor. Rastgele görünen bazı sonuçlar üretir, ancak Lehmer jeneratörünün garantileri geçerli değildir.
aaaaaaaaaaaa

1
createRandomNumberİşlevi hemen hemen her şeyi yanlış, en önemlisi yeni bir RNG denir her zaman, peş peşe çağrılar hep aynı şamandıra kullanacağı araçlar başlatır yapar sonraki bir ektir. Verilen kodunda onun için neredeyse imkansız 'a'bir şey ama eşlenecek '1've 'red'.
aaaaaaaaaaaa

-3

Tamam, işte kararlaştırdığım çözüm.

Önce "newseed ()" işlevini kullanarak bir çekirdek değer yaratırsınız. Daha sonra çekirdek değerini "srandom ()" işlevine iletirsiniz. Son olarak, "srandom ()" işlevi 0 ile 1 arasında sözde rastgele bir değer döndürür.

Önemli olan, çekirdek değerin bir dizi içinde depolanmasıdır. Basitçe bir tamsayı veya kayan nokta olsaydı, işlev her çağrıldığında değerin üzerine yazılırdı, çünkü tamsayılar, yüzer sayılar, dizeler ve benzerlerinin değerleri, dizilerde olduğu gibi sadece işaretçiler yerine doğrudan yığında saklanır ve diğer nesneler. Böylece tohumun değerinin kalıcı kalması mümkündür.

Son olarak, "srandom ()" işlevini "Math" nesnesinin bir yöntemi olacak şekilde tanımlamak mümkündür, ancak bunu çözmeyi size bırakacağım. ;)

İyi şanslar!

JavaScript:

// Global variables used for the seeded random functions, below.
var seedobja = 1103515245
var seedobjc = 12345
var seedobjm = 4294967295 //0x100000000

// Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
{
    return [seednum]
}

// Works like Math.random(), except you provide your own seed as the first argument.
function srandom(seedobj)
{
    seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
    return seedobj[0] / (seedobjm - 1)
}

// Store some test values in variables.
var my_seed_value = newseed(230951)
var my_random_value_1 = srandom(my_seed_value)
var my_random_value_2 = srandom(my_seed_value)
var my_random_value_3 = srandom(my_seed_value)

// Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
WScript.Echo(my_random_value_1)
WScript.Echo(my_random_value_2)
WScript.Echo(my_random_value_3)

Lua 4 (kişisel hedef ortamım):

-- Global variables used for the seeded random functions, below.
seedobja = 1103515.245
seedobjc = 12345
seedobjm = 4294967.295 --0x100000000

-- Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
    return {seednum}
end

-- Works like random(), except you provide your own seed as the first argument.
function srandom(seedobj)
    seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
    return seedobj[1] / (seedobjm - 1)
end

-- Store some test values in variables.
my_seed_value = newseed(230951)
my_random_value_1 = srandom(my_seed_value)
my_random_value_2 = srandom(my_seed_value)
my_random_value_3 = srandom(my_seed_value)

-- Print the values to console.
print(my_random_value_1)
print(my_random_value_2)
print(my_random_value_3)

Not - Stack Overflow'a henüz pek aşina değilim, ancak gönderiler neden kronolojik sıraya göre değil?
posfan12

Merhaba @ posfan12 - Sorulara verilen yanıtlar genellikle "krem en üste çıkacak" şekilde "olumlu oylar" şeklinde sıralanır. Ancak, aynı puana sahip cevapların adil bir şekilde görüntülenmesini sağlamak için, rastgele sırayla gösterilirler. Bu aslında benim sorum olduğundan ;-) kesinlikle kısa bir süre sonra kontrol edeceğim. Eğer ben (veya başkaları) bu cevabı yararlı bulursam, ona oy veririz ve eğer cevabın "doğru" olduğunu düşünürsem, bu cevaba da yeşil bir onay işareti eklenir. - StackOverflow'a hoş geldiniz!
scunliffe

3
-1 Bu LCG uygulaması, seedobj[0] * seedobja2 ^ 53'ten büyük bir sayı ile sonuçlanması muhtemel olduğundan , JavaScript'teki tam tamsayı sınırını bozar . Sonuç, sınırlı bir çıktı aralığı ve bazı tohumlar için muhtemelen çok kısa bir dönemdir.
aaaaaaaaaaaa
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.