Bir aralıktan rastgele tamsayı oluşturma


158

Verilen aralıkta (sınır değerleri dahil) rasgele bir tamsayı üretecek bir fonksiyona ihtiyacım var. Mantıksız kalite / rastgelelik gereklilikleri yok, dört gereksinimim var:

  • Hızlı olması lazım. Projemin milyonlarca (hatta bazen on milyonlarca) rastgele sayı üretmesi gerekiyor ve şu anki jeneratör fonksiyonumun bir darboğaz olduğu kanıtlandı.
  • Ben makul düzgün olması gerekir (rand () kullanımı gayet iyi).
  • min-maks aralıklar <0, 1> ila <-32727, 32727> arasında herhangi bir şey olabilir.
  • görülebilmelidir.

Şu anda aşağıdaki C ++ kodu var:

output = min + (rand() * (int)(max - min) / RAND_MAX)

Sorun şu ki, gerçekten üniform değil - max sadece rand () = RAND_MAX (Visual C ++ için 1/32727) olduğunda döndürülür. Bu, son değerin neredeyse hiç döndürülmediği <-1, 1> gibi küçük aralıklar için büyük bir sorundur.

Bu yüzden kalem ve kağıt aldım ve aşağıdaki formülü ((int) (n + 0.5) tamsayı yuvarlama hilesi üzerine inşa etti) buldum:

resim açıklamasını buraya girin

Ama yine de bana düzgün bir dağılım vermiyor. 10000 numuneyle tekrarlanan çalışmalar, -1, 0.1 değerleri için bana 37:50:13 oranını verir.

Daha iyi formül önerebilir misiniz? (hatta tüm sahte rasgele sayı üreteci işlevi)



3
@ Fatura MaGriff: evet. Aynı problemi var. Basitleştirilmiş bir versiyon: 10 şekeri 3 çocuk arasında eşit olarak nasıl bölebilirsiniz (şekerleri kırmadan)? Cevap, yapamazsınız - her çocuğa üç tane vermeniz ve onuncu çocuğu kimseye vermemeniz gerekir.
Jerry Coffin,

5
Baktığınız Boost.Random ?
Fred Nurk

3
"Neredeyse hiç doğru şekilde çözülmeyen basit bir sorun" başlıklı Andrew Koenig makalesine bakın: drdobbs.com/blog/archives/2010/11/a_simple_proble.html
Gene Bushuyev

1
@Gene Bushuyev: Hem Andrew hem de ben bu konuya uzun zamandır değiniyoruz. Bkz. Groups.google.com/group/comp.lang.c++/browse_frm/thread/… ve: groups.google.com/group/comp.os.ms-windows.programmer.tools.mfc/…
Jerry Tabut

Yanıtlar:


105

Hızlı, sizinkinden biraz daha iyi, ama yine de düzgün bir şekilde dağılmamış bir çözüm

output = min + (rand() % static_cast<int>(max - min + 1))

Aralığın boyutu 2'lik bir güç dışında, bu yöntem , kalitesinden bağımsız olarak önyargılı düzgün olmayan dağıtılmış sayılar üretirrand() . Bu yöntemin kalitesinin kapsamlı bir testi için lütfen bunu okuyun .


2
Teşekkürler, bu hızlı testlerden benim için yeterince iyi görünüyor - -1, 0, 1 için dağılımı yaklaşık 33:33:33.
Matěj Zábský

3
Her zaman maksimum değeri döndürür. Burada bir şey mi kaçırıyorum? : |
rohan-patel

15
rand()C ++ ' da zararlı olarak kabul edilmelidir , düzgün dağıtılmış ve aslında rastgele bir şey elde etmenin çok daha iyi yolları vardır.
Mgetz

1
Zamanın% 100'ünde gerçekten doğru bir sayı döndürüyor mu? Burada "doğru şekilde" yapmak için özyineleme kullanan başka bir stackoverflow yanıtı buldum: stackoverflow.com/a/6852396/623622
Czarek Tomczak

2
Pek çok yeni okuyucu için güvenilir bir bilgi kaynağı gibi görünen oldukça arzulanan bir cevap olduğundan, bu çözümün kalitesinden ve potansiyel tehlikelerinden bahsetmenin çok önemli olduğunu düşünüyorum, bu yüzden bir düzenleme yaptım.
plasmacel

297

En basit (ve dolayısıyla en iyi) C ++ (2011 standardını kullanarak) yanıtı

#include <random>

std::random_device rd;     // only used once to initialise (seed) engine
std::mt19937 rng(rd());    // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased

auto random_integer = uni(rng);

Tekerleği yeniden icat etmeye gerek yok. Önyargı konusunda endişelenmenize gerek yok. Zamanı rastgele tohum olarak kullanma konusunda endişelenmenize gerek yok.


1
Bugünlerde bu cevap olmalı . Daha fazla özellik için sözde rasgele sayı oluşturma referansı .
alextoind

8
Ben "en iyi" değil, en basit (ve en deyimsel) üzerinde anlaşıyorum. Ne yazık ki Standart, bazı durumlardarandom_device tamamen kırılabilecek bir garanti vermemektedir . Dahası, çok iyi bir genel amaçlı seçim olsa da, kaliteli jeneratörlerin en hızlısı değildir ( bu karşılaştırmaya bakın ) ve bu nedenle OP için ideal bir aday olmayabilir. mt19937
Alberto M

1
@AlbertoM Ne yazık ki, atıfta bulunduğunuz karşılaştırma yeterli ayrıntı sağlamaz ve tekrarlanabilir değildir, bu da şüpheli kılar (dahası, 2015'ten itibaren, cevabım 2013'e kadar uzanır). Etrafında daha iyi yöntemler olduğu doğru olabilir (ve umarım gelecekte minstdböyle bir yöntem olacaktır), ancak bu ilerleme. Kötü uygulanması ile ilgili random_device- bu korkunç ve bir hata olarak kabul edilmelidir (eğer izin verirse muhtemelen C ++ standardı).
Walter

1
Sana tamamiyle katılıyorum; Aslında Çözümünüzü eleştirmek istemediğini se başına sadece C ++, 11 vaatlerine rağmen konuda kesin cevap, henüz yazılacak olduğu gündelik okuyucuyu uyarmak istedim. 2015 yılından itibaren konuyla ilgili bir sorunun cevabı olarak konuya genel bir bakış göndereceğim .
Alberto M

1
Bu "en basit" mi? Açıkça daha basit olanın neden rand()bir seçenek olmadığını ve rastgele bir pivot indeksi oluşturmak gibi kritik olmayan kullanım için önemli olduğunu açıklayabilir misiniz ? Ayrıca, sıkı bir döngü / satır içi işlevde random_device/ mt19937/ oluşturma konusunda endişelenmem gerekiyor uniform_int_distributionmu? Onları etrafta geçirmeyi tercih etmeli miyim?
bluenote10

60

Derleyiciniz C ++ 0x'i destekliyorsa ve bunu kullanmak sizin için bir seçenekse, yeni standart <random>başlığın ihtiyaçlarınızı karşılaması muhtemeldir. uniform_int_distributionMinimum ve maksimum sınırları (ihtiyacınız dahil) kabul edecek yüksek bir kaliteye sahiptir ve bu dağıtıma takmak için çeşitli rasgele sayı jeneratörleri arasından seçim yapabilirsiniz.

İşte int[-57, 365] 'de muntazam bir şekilde dağıtılan bir milyon rasgele s üreten kod . <chrono>Performansın sizin için büyük bir endişe olduğunu belirttiğiniz için yeni std tesislerini zamanlamak için kullandım.

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    Clock::time_point t0 = Clock::now();
    const int N = 10000000;
    typedef std::minstd_rand G;
    G g;
    typedef std::uniform_int_distribution<> D;
    D d(-57, 365);
    int c = 0;
    for (int i = 0; i < N; ++i) 
        c += d(g);
    Clock::time_point t1 = Clock::now();
    std::cout << N/sec(t1-t0).count() << " random numbers per second.\n";
    return c;
}

Benim için (2,8 GHz Intel Core i5) çıktı:

2.10268e + 07 saniyede rastgele sayılar.

Jeneratörü yapıcısına bir int ileterek ekebilirsiniz:

    G g(seed);

Daha sonra int, dağıtımınız için ihtiyacınız olan aralığı kapsamadığını fark ederseniz, bu, aşağıdaki uniform_int_distributiongibi değiştirilerek giderilebilir (örn. İçin long long):

    typedef std::uniform_int_distribution<long long> D;

Daha sonra minstd_rand, yeterince yüksek kaliteli bir jeneratör olmadığını fark ederseniz , bu kolayca değiştirilebilir. Örneğin:

    typedef std::mt19937 G;  // Now using mersenne_twister_engine

Rastgele sayı üreteci ve rastgele dağılım üzerinde ayrı bir kontrole sahip olmak oldukça özgürleştirici olabilir.

Ben de (gösterilmemiştir) bu dağıtımın ilk 4 "anlarını" hesapladım (kullanarak minstd_rand) ve dağıtımın kalitesini ölçmek için bunları teorik değerlerle karşılaştırdım :

min = -57
max = 365
mean = 154.131
x_mean = 154
var = 14931.9
x_var = 14910.7
skew = -0.00197375
x_skew = 0
kurtosis = -1.20129
x_kurtosis = -1.20001

( x_Önek "beklenen" anlamına gelir)


3
Bu yanıt, yalnızca bir aralıktan rastgele bir tamsayı oluşturmak için gereken kodu gösteren kısa bir özet kod snippet'i kullanabilir.
arekolek

Sorun, dağılımın minimum ve maksimum değerlerinin asla değişmemesi gerçeğiyle daha kolaydır. dHer yinelemede farklı sınırlarla oluşturmak zorunda olsaydınız ne olurdu ? Döngüyü ne kadar yavaşlatır?
quant_dev

16

Sorunu iki kısma ayıralım:

  • n0 ila (maks-min) aralığında rastgele bir sayı oluşturun .
  • Bu sayıya min ekle

İlk bölüm en zor olanı. Diyelim ki rand () dönüş değeri mükemmel bir şekilde eşittir. Modulo kullanılması ilk (RAND_MAX + 1) % (max-min+1)sayılara sapma katacaktır . Biz sihirli değişebilir Yani RAND_MAXiçin RAND_MAX - (RAND_MAX + 1) % (max-min+1), artık hiçbir önyargı olacaktır.

Algoritmamızın çalışma zamanına sözde-belirsizliğe izin vermek istiyorsak bu sezgiyi kullanabileceğimiz ortaya çıkıyor. Rand () çok büyük bir sayı döndürdüğünde, yeterince küçük olana kadar rastgele başka bir sayı isteriz.

Çalışma süresi şimdi olduğu geometrik dağıtılmış beklenen değerle, ilk denemede yeterince küçük bir sayı alma olasılığıdır. Yana hep daha az olduğunu , bunu biliyoruz tekrarlamalar beklenen numarası her zaman daha az herhangi bir aralığı için ikiden olacak, böylece. Bu teknikle standart bir CPU üzerinde bir saniyeden az bir sürede on milyonlarca rastgele sayı üretmek mümkün olmalıdır.1/ppRAND_MAX - (RAND_MAX + 1) % (max-min+1)(RAND_MAX + 1) / 2p > 1/2

DÜZENLE:

Yukarıdakiler teknik olarak doğru olmasına rağmen, DSimon'un cevabı muhtemelen pratikte daha yararlıdır. Bu şeyleri kendiniz uygulamamalısınız. Reddetme örneklemesinin birçok uygulamasını gördüm ve bunun doğru olup olmadığını görmek genellikle çok zordur.



3
İlginç gerçek: Joel Spolsky bir zamanlar bu sorunun bir versiyonundan StackOverflow'un yanıt vermede neyin iyi olduğuna bir örnek olarak bahsetti. O anda hem site kapsayan ret örneklemeye cevapların tamamını baktı ve her tek tek yanlış.
Jørgen Fogh

13

Mersenne Twister'a ne dersin ? Boost uygulamasının kullanımı oldukça kolaydır ve birçok gerçek dünya uygulamasında iyi test edilmiştir. Yapay zeka ve evrimsel algoritmalar gibi çeşitli akademik projelerde kendim kullandım.

Altı taraflı bir kalıbı yuvarlamak için basit bir işlev yaptıkları örnekler:

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>

boost::mt19937 gen;

int roll_die() {
    boost::uniform_int<> dist(1, 6);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > die(gen, dist);
    return die();
}

Oh, ve işte bu jeneratörü biraz daha fazla şımartmak, eğer ikna olmamanız durumunda, onu daha aşağıda kullanmanız gerekir rand():

Mersenne Twister, Makoto Matsumoto ve Takuji Nishimura tarafından icat edilen bir "rastgele sayı" jeneratörüdür; web siteleri algoritmanın çok sayıda uygulamasını içerir.

Esasen, Mersenne Twister çok büyük bir lineer geri besleme kaydırma yazmacıdır. Algoritma, 32 bit işaretsiz tam sayılardan oluşan 624 elementlik bir dizide saklanan 19.937 bitlik bir tohum üzerinde çalışır. 2 ^ 19937-1 değeri bir Mersenne üssüdür; tohumu manipüle etme tekniği daha eski bir "büküm" algoritmasına dayanır - dolayısıyla "Mersenne Twister" adı.

Mersenne Twister'ın çekici bir yönü, sayı üretmek için - zaman alıcı çarpımın aksine - ikili işlemleri kullanmasıdır. Algoritma ayrıca çok uzun bir süreye ve iyi bir ayrıntı düzeyine sahiptir. Kriptografik olmayan uygulamalar için hem hızlı hem de etkilidir.


1
Mersenne twister iyi bir jeneratör, ancak altta yatan jeneratörün kendisinden bağımsız olarak uğraştığı sorun devam ediyor.
Jerry Coffin,

Boost'u sadece rastgele jeneratör için kullanmak istemiyorum, çünkü (projem bir kütüphane olduğu için) projeye başka bir bağımlılık getirmek anlamına geliyor. Muhtemelen gelecekte yine de kullanmak zorunda kalacağım, bu yüzden bu jeneratöre geçebilirim.
Matěj Zábský

1
@Jerry Coffin Hangi problem? Teklif ettim çünkü tüm gereksinimlerini karşıladı: hızlı, tekdüze ( boost::uniform_intdağıtımı kullanarak ), min max aralıklarını istediğiniz herhangi bir şeye dönüştürebilirsiniz ve görülebilir.
Aphex

@mzabsky Muhtemelen beni durdurmasına izin vermezdim, projelerimi gönderilmek üzere profesörlerime göndermek zorunda kaldığımda, kullandığım ilgili destek başlığı dosyalarını ekledim; 40mb boost kütüphanesinin tamamını kodunuzla birlikte paketlemeniz gerekmemelidir. Elbette sizin durumunuzda bu, telif hakkı gibi diğer nedenlerle mümkün olmayabilir ...
Aphex

@Aphex Benim proje gerçekten çok tekdüze dağılım ihtiyacı bilimsel bir simülatör veya bir şey değildir. Eski jeneratörü 1,5 yıl boyunca herhangi bir sorun olmadan kullandım, sadece çok küçük aralıktan sayılar üretmek için ilk kez ihtiyacım olduğunda önyargılı dağılımı fark ettim (bu durumda 3). Yine de hız, yükseltme çözümünü dikkate almak için bir argüman. Projeme sadece birkaç gerekli dosyayı ekleyip ekleyemeyeceğimi görmek için lisansına bakacağım - şu anda olduğu gibi "Ödeme -> F5 -> kullanıma hazır" ı seviyorum.
Matěj Zábský

11
int RandU(int nMin, int nMax)
{
    return nMin + (int)((double)rand() / (RAND_MAX+1) * (nMax-nMin+1));
}

Bu, 32768 tamsayıların (nMax-nMin + 1) tamsayılarla eşleştirilmesidir. (NMax-nMin + 1) küçükse (gereksiniminizdeki gibi) haritalama oldukça iyi olacaktır. Ancak (nMax-nMin + 1) büyükse, eşlemenin çalışmadığını unutmayın (Örneğin, 32768 değerlerini eşit olasılıkla 30000 değerlerle eşleyemezsiniz). Bu tür aralıklar gerekiyorsa - 15 bit rand () yerine 32 bit veya 64 bit rasgele bir kaynak kullanmalı veya aralık dışındaki rand () sonuçlarını yok saymalısınız.


Popülerliğine rağmen, bilimsel olmayan projelerim için de kullandığım şey bu. Anlaşılması kolay (matematik derecesine ihtiyacınız yok) ve yeterli performans sergiliyor (bunu kullanarak hiçbir kodu profillemek zorunda kalmadınız). :) Büyük aralıklar durumunda, iki rand () değerini bir araya getirebilir ve çalışmak için 30 bitlik bir değer alabiliriz (RAND_MAX = 0x7fff, yani 15 rastgele bit varsayarak)
efotinis

tamsayı taşması uyarısını önlemek RAND_MAXiçin (double) RAND_MAXolarak değiştirin .
alex

4

İşte sayıları üreten tarafsız bir sürüm [low, high]:

int r;
do {
  r = rand();
} while (r < ((unsigned int)(RAND_MAX) + 1) % (high + 1 - low));
return r % (high + 1 - low) + low;

Aralığınız oldukça küçükse, karşılaştırmanın sağ tarafını dodöngüde önbelleğe almak için bir neden yoktur .


IMO, sunulan çözümlerin hiçbiri gerçekten çok fazla gelişme değil. Döngü tabanlı çözümü çalışıyor, ancak özellikle OP gibi küçük bir aralık için oldukça verimsiz olması muhtemel. Tek tip sapma çözümü aslında tek tip sapmalar üretmiyor . En fazla, tekdüzelik eksikliğini kamufle eder.
Jerry Coffin,

@ Jerry: Lütfen yeni sürümü kontrol edin.
Jeremiah Willcock

Bunun doğru çalışması konusunda biraz emin değilim. Belki de olabilir, ama doğruluk en azından bana göre belli değil.
Jerry Coffin,

@ Jerry: İşte benim mantığım: Sıralamanın [0, h)sadelik için olduğunu varsayın . Çağrı rand()sahip RAND_MAX + 1olası dönüş değerlerini; alarak rand() % hçöker (RAND_MAX + 1) / hher bunların hdışında, çıkış değerlerinin (RAND_MAX + 1) / h + 1tanesi daha az olan değerler ile eşleştirilir (RAND_MAX + 1) % h(nedeniyle aracılığıyla son kısmi döngüsünün hçıkışlar). Bu nedenle (RAND_MAX + 1) % htarafsız bir dağıtım elde etmek için olası çıktıları kaldırıyoruz .
Jeremiah Willcock


1

min ve max'ın int değerler olduğunu varsayalım, [ve] bu değeri içerdiği anlamına gelir, (ve) bu değeri içermediği anlamına gelir, c ++ rand () kullanarak doğru değeri elde etmek için yukarıdakileri kullanın

reference: for () [] tanımlayın, şu adresi ziyaret edin:

https://en.wikipedia.org/wiki/Interval_(mathematics)

rand ve srand işlevi veya RAND_MAX tanımlamak için şu adresi ziyaret edin:

http://en.cppreference.com/w/cpp/numeric/random/rand

[en az en çok]

int randNum = rand() % (max - min + 1) + min

(en az en çok]

int randNum = rand() % (max - min) + min + 1

[en az en çok)

int randNum = rand() % (max - min) + min

(en az en çok)

int randNum = rand() % (max - min - 1) + min + 1

0

Bu iş parçacığında reddetme örneklemesi zaten tartışıldı, ancak rand() % 2^somethingyukarıda belirtildiği gibi herhangi bir önyargı getirmeyen gerçeğe dayanan bir optimizasyon önermek istedim .

Algoritma gerçekten basit:

  • aralık uzunluğundan daha büyük 2'nin en küçük gücünü hesaplayın
  • bu "yeni" aralıkta bir sayıyı rastgele
  • orijinal aralığın uzunluğundan azsa bu sayıyı döndürür
    • aksini reddet

İşte benim örnek kod:

int randInInterval(int min, int max) {
    int intervalLen = max - min + 1;
    //now calculate the smallest power of 2 that is >= than `intervalLen`
    int ceilingPowerOf2 = pow(2, ceil(log2(intervalLen)));

    int randomNumber = rand() % ceilingPowerOf2; //this is "as uniform as rand()"

    if (randomNumber < intervalLen)
        return min + randomNumber;      //ok!
    return randInInterval(min, max);    //reject sample and try again
} 

Bu, özellikle küçük aralıklar için iyi çalışır, çünkü 2'nin gücü, gerçek aralık uzunluğuna "daha yakın" olacaktır ve böylece özlem sayısı daha az olacaktır.

PS
Açıkçası özyinelemeden kaçınmak daha verimli olacaktır (günlük tavan üzerinde tekrar tekrar hesaplamaya gerek yok ..) ama bu örnek için daha okunabilir olduğunu düşündüm.


0

Çoğu öneride, genellikle 0'dan RAND_MAX'a kadar olan rand () işlevinden aldığınız ilk rasgele değerin boşa harcandığına dikkat edin. Sizden sadece bir rastgele sayı oluşturuyorsunuz, size daha fazlasını verebilecek sağlam bir prosedür var.

Tamsayı rasgele sayıların [min, maks] bölgesini istediğinizi varsayalım. [0, maks-dak] 'dan başlıyoruz

B tabanını al = maks-min + 1

B tabanındaki rand () 'dan aldığınız bir sayıyı temsil ederek başlayın.

Bu şekilde zemine sahip olursunuz (log (b, RAND_MAX)) çünkü b tabanındaki her rakam, muhtemelen sonuncusu hariç, [0, max-min] aralığında rastgele bir sayıyı temsil eder.

Tabii ki [min, maks] 'e son geçiş her rastgele sayı r + dak için basittir.

int n = NUM_DIGIT-1;
while(n >= 0)
{
    r[n] = res % b;
    res -= r[n];
    res /= b;
    n--;
}

NUM_DIGIT, b tabanındaki ayıklayabileceğiniz basamak sayısı ve

NUM_DIGIT = floor(log(b,RAND_MAX))

o zaman yukarıdaki, b <RAND_MAX sağlayan bir RAND_MAX rasgele sayıdan 0 ile b-1 arasında NUM_DIGIT rasgele sayı çıkarmanın basit bir uygulamasıdır.


-1

Bunun formülü çok basit, bu yüzden bu ifadeyi deneyin,

 int num = (int) rand() % (max - min) + min;  
 //Where rand() returns a random number between 0.0 and 1.0

2
Bütün sorun C / C ++ 'ın rand'ı çalışma zamanı tarafından belirtilen bir aralıkta döndüren kullanmaktı. Bu iş parçacığında gösterildiği gibi, istatistiksel özelliklerini veya performanslarını yok etmekten kaçınmak istiyorsanız, [0, RAND_MAX] ile [MIN, MAX] arasındaki rastgele tam sayıları eşlemek tamamen kolay değildir. [0, 1] aralığında iki katınız varsa, haritalama kolaydır.
Matěj Zábský

2
Cevabınız yanlış, bunun yerine modülü kullanmalısınız:int num = (int) rand() % (max - min) + min;
Jaime Ivan Cervantes

-2

Yanılmıyorsam aşağıdaki ifade tarafsız olmalıdır:

std::floor( ( max - min + 1.0 ) * rand() ) + min;

Burada rand () size 1.0 dahil 0.0 ve 1.0 DEĞİL aralığında rastgele bir değer verdiğini ve max ve min min <max koşulu ile tamsayı olduğunu varsayıyorum.


std::floordöner doubleve burada bir tamsayı değerine ihtiyacımız var. Ben intkullanmak yerine sadece döküm yapardı std::floor.
musiphil
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.