O (1) 'de benzersiz (tekrar etmeyen) rastgele sayılar?


179

Ben asla tekrar (yani 6 iki kez görünmüyor) 0 ve 1000 arasında benzersiz rasgele sayılar oluşturmak istiyorum, ama bunu yapmak için önceki değerlerin O (N) arama gibi bir şeye başvurmaz. Mümkün mü?



2
0, 0 ile 1000 arasında mı?
Pete Kirkham

4
Sabit bir süre boyunca ( O(n)zaman veya hafıza gibi ) herhangi bir şeyi yasaklıyorsanız , kabul edilen cevap da dahil olmak üzere aşağıdaki cevabın çoğu yanlıştır.
jww

Bir kart grubunu nasıl karıştırırsınız?
Albay Panik

9
UYARI! Gerçekten rastgele diziler üretmemek için aşağıda verilen cevapların çoğu O (n) 'den daha yavaştır veya başka şekilde kusurludur! codinghorror.com/blog/archives/001015.html , herhangi birini kullanmadan veya kendinizle uyuşmaya çalışmadan önce önemli bir okuma!
ivan_pozdeev

Yanıtlar:


247

0-1000 değerleriyle 1001 tamsayılar dizisini başlatın ve dizinin geçerli maksimum dizinine (1000 ile başlayan) bir değişken max ayarlayın. Rasgele bir sayı seçin, r, 0 ile maks. Arasında, r konumundaki sayıyı max konumundaki sayıyla değiştirin ve şimdi sayıyı max konumunda döndürün. Maks. 1 azaltın ve devam edin. Max 0 olduğunda, max değerini dizinin boyutuna geri getirin - 1 ve diziyi yeniden başlatmaya gerek kalmadan yeniden başlayın.

Güncelleme: Soruyu cevaplarken bu yöntemi kendi başıma bulsam da , bazı araştırmalardan sonra bunun Durstenfeld-Fisher-Yates veya Knuth-Fisher-Yates olarak bilinen Fisher-Yates'in değiştirilmiş bir versiyonu olduğunu fark ettim . Açıklamayı takip etmek biraz zor olabileceğinden, aşağıda bir örnek verdim (1001 yerine 11 eleman kullanarak):

Dizi, [n] = n dizisine başlatılan 11 öğeyle başlar, maks. 10'da başlar:

+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
                                ^
                               max    

Her yinelemede, 0 ile max arasında rastgele bir r sayısı seçilir, dizi [r] ve dizi [max] değiştirilir, yeni dizi [max] döndürülür ve max azaltılır:

max = 10, r = 3
           +--------------------+
           v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 9, r = 7
                       +-----+
                       v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 8, r = 1
     +--------------------+
     v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 7, r = 5
                 +-----+
                 v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

...

11 yinelemeden sonra dizideki tüm sayılar seçildi, max == 0 ve dizi öğeleri karıştırıldı:

+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

Bu noktada, maks 10'a sıfırlanabilir ve işlem devam edebilir.


6
Jeff'in karıştırma hakkındaki gönderisi, bunun iyi rastgele sayılar döndürmeyeceğini gösteriyor. Codinghorror.com/blog/archives/001015.html
pro

14
@Peter Rounce: Sanmıyorum; bu bana Jeff'in gönderisinde (iyi adam olarak) alıntılanan Fisher Yates algoritması gibi görünüyor.
Brent.Longborough

3
@robert: Sorunun adında olduğu gibi "O (1) 'de benzersiz rastgele sayılar" üretmediğini belirtmek istedim.
Charles

3
@mikera: Kabul edildi, ancak teknik olarak sabit boyutlu tamsayılar kullanıyorsanız, tüm liste O (1) olarak üretilebilir (büyük bir sabitle, yani 2 ^ 32). Ayrıca, pratik amaçlar için, "rastgele" tanımı önemlidir - eğer sisteminizin entropi havuzunu gerçekten kullanmak istiyorsanız, sınır hesaplamaların kendisinden ziyade rastgele bitlerin hesaplanmasıdır ve bu durumda n log n konuyla ilgilidir. tekrar. Ancak / dev / random yerine / dev / urandom (eşdeğeri) kullanmanız muhtemel durumda, 'pratik olarak' O (n) 'ye geri dönersiniz.
Charles

4
Biraz kafam karıştı, Nher seferinde istenen sonucu elde etmek için yinelemeleri (bu örnekte 11) yapmanız gerekmiyor O(n)mu? Aynı başlangıç ​​durumundan kombinasyonlar Nalmak için yinelemeler yapmanız gerektiğinden N!, çıktınız yalnızca N durumundan biri olacaktır.
Seph

71

Bunu yapabilirsiniz:

  1. Bir liste oluşturun, 0..1000.
  2. Listeyi karıştırın. ( Bunu yapmanın iyi bir yolu için Fisher-Yates shuffle'a bakın .)
  3. Numaraları karıştırılan listeden sırayla döndürün.

Bu nedenle, her seferinde eski değerlerin aranması gerekmez, ancak yine de ilk karıştırma için O (N) gerekir. Ancak Nils yorumlarda belirtildiği gibi, bu amortismana tabi tutulur O (1).


5
Sadece Bazı N = 1000, yani O (N / N) olduğunu söylüyorsun O (1)
Guvante

1
Karıştırılan diziye her ekleme bir işlemse, 1 değer ekledikten sonra 1 rastgele değer elde edebilirsiniz. 2 değer için 2, vb. N değer için n. Listeyi oluşturmak n işlemi gerektirir, bu nedenle tüm algoritma O (n) olur. 1.000.000 rasgele değere ihtiyacınız varsa, 1.000.000 ops sürecektir
Kibbee

3
Bu şekilde düşünün, eğer sabit bir zaman olsaydı, 10 rastgele sayı için 10 milyarla aynı zaman alacaktı. Ancak karıştırma (O (n)) nedeniyle bunun doğru olmadığını biliyoruz.
Kibbee

1
N lg n rasgele bit üretmeniz gerektiğinden, bu aslında amortize edilmiş O (log n) süresini alır.
Charles

2
Ve şimdi, bunu yapmak için tüm gerekçelere sahibim! meta.stackoverflow.com/q/252503/13
Chris Jester-Young

60

Maksimum Doğrusal Geri Bildirim Vardiya Kaydı Kullanma .

Birkaç C hattında uygulanabilir ve çalışma zamanında birkaç test / daldan biraz daha fazlasını, biraz ekleme ve bit kaydırma yapar. Rastgele değil, ama çoğu insanı kandırıyor.


12
"Bu rastgele değil, ama çoğu insanı kandırıyor". Bu, tüm sahte rasgele sayı üreteçleri ve bu soruya uygulanabilir tüm cevaplar için geçerlidir. Ancak çoğu insan bunu düşünmez. Yani bu notu atlamak belki daha fazla vokyo ile sonuçlanır ...
f3lix

3
@bobobobo: O (1) hafızanın sebebi budur.
Ash

3
Nit: O (log N) hafızası.
Paul Hankin

2
Bu yöntemi kullanarak, 0 ile 800000 arasında sayıları nasıl oluşturursunuz? Bazıları 1048575 (2 ^ 20-1) olan bir LFSR kullanabilir ve sayı aralık dışındaysa bir sonraki LFSR'yi kullanabilir, ancak bu etkili olmayacaktır.
tigrou

1
Bir LFSR olarak, bu eşit olarak dağıtılmış diziler üretmez : üretilecek dizinin tamamı ilk eleman tarafından tanımlanır.
ivan_pozdeev

21

Doğrusal Bir Eşlenik Jeneratör kullanabilirsiniz . m(Modül) 1000'den büyük en yakın asal nerede olacaktır. Aralıktan bir sayı aldığınızda, bir sonrakini alın. Sekans yalnızca tüm öğeler meydana geldiğinde tekrarlanır ve tablo kullanmanız gerekmez. Yine de bu jeneratörün dezavantajlarının farkında olun (rasgelelik eksikliği dahil).


1
1009 1000'den sonraki ilk başbakan.
Teepeemm

Bir LCG, ardışık sayılar arasında yüksek korelasyona sahiptir, bu nedenle kombinasyonlar büyük ölçüde oldukça rasgele olmayacaktır (örneğin k, sekanstan ayrı olandan daha uzak sayılar hiçbir zaman birlikte meydana gelemez).
ivan_pozdeev

m, 1001 (sıfır için 1000 + 1) eleman sayısı olmalıdır ve Next = (1002 * Current + 757) mod 1001'i kullanabilirsiniz;
Max Abramovich

21

Sen kullanabilirsiniz Biçim-Koruma ŞifrelemeBir sayacı şifrelemek kullanabilirsiniz. Sayacınız 0'dan yukarı doğru gider ve şifreleme, istediğiniz yarıçap ve genişliğin rastgele görünen bir değerine dönüştürmek için seçtiğiniz bir anahtarı kullanır. Örneğin bu sorudaki örnek için: taban 10, genişlik 3.

Blok şifreleri normal olarak 64 veya 128 bitlik sabit bir blok boyutuna sahiptir. Ancak Biçim Korumalı Şifreleme, AES gibi standart bir şifre almanıza ve hala şifreleme açısından sağlam bir algoritma ile istediğiniz yarıçap ve genişlikten daha küçük genişlikte bir şifre oluşturmanıza olanak tanır.

Asla çarpışma olmaması garanti edilir (çünkü kriptografik algoritmalar 1: 1 eşleme oluşturur). Ayrıca tersine çevrilebilir (2 yönlü bir eşleme), böylece elde edilen sayıyı alıp başladığınız sayaç değerine geri dönebilirsiniz.

Bu tekniğin, karıştırılmış diziyi vb. Depolamak için belleğe ihtiyacı yoktur, bu sınırlı belleğe sahip sistemlerde bir avantaj olabilir.

AES-FFX bunu başarmak için önerilen standart bir yöntemdir. Tamamen uygun olmasa da, AES-FFX fikrine dayanan bazı temel Python kodlarını denedim - burada Python koduna bakın . Örneğin, bir sayacı rastgele görünen 7 basamaklı ondalık sayıya veya 16 bit sayıya şifreleyebilir. Aşağıda, soru 10 olarak genişlik 10, genişlik 3 (0 ile 999 arasında bir sayı vermek için) örneği verilmiştir:

000   733
001   374
002   882
003   684
004   593
005   578
006   233
007   811
008   072
009   337
010   119
011   103
012   797
013   257
014   932
015   433
...   ...

Farklı yinelenmeyen sahte rastgele diziler elde etmek için şifreleme anahtarını değiştirin. Her şifreleme anahtarı farklı bir yinelenmeyen sahte rastgele sıra oluşturur.


Bu temelde basit bir eşlemedir, bu nedenle LCG ve LFSR'den farklı değildir, tüm ilgili bükülmelerle (örneğin k, sekansta ayrı olandan fazla değerler asla birlikte meydana gelemez).
ivan_pozdeev

@ivan_pozdeev: Yorumunuzun anlamını anlamakta güçlük çekiyorum. Bu haritalamada neyin yanlış olduğunu, "ilgili tüm karışıklıkların" ne olduğunu ve ne olduğunu açıklayabilir misiniz k?
Craig McQueen

Burada etkili bir şekilde yapılan tüm "şifreleme", diziyi 1,2,...,N, başka, ama yine de sabit bir sırada aynı sayıların bir dizisi ile değiştirmektir . Numaralar daha sonra bu diziden birer birer çekilir. ktoplanan değerlerin sayısıdır (OP bunun için bir harf belirtmedi, bu yüzden birini tanıtmak zorunda kaldım).
ivan_pozdeev

3
@ivan_pozdeev FPE'nin belirli bir statik eşleme uygulaması gerektiği veya "döndürülen kombinasyonun tam olarak ilk sayı ile tanımlandığı" durumda değildir. Konfigürasyon parametresi ilk sayının boyutundan çok daha büyük olduğundan (sadece bin duruma sahiptir), aynı başlangıç ​​değeri ile başlayan ve daha sonra farklı sonraki değerlere ilerleyen birden fazla dizi olmalıdır. Herhangi bir gerçekçi jeneratör olası tüm permütasyon alanını kapatamaz; OP istemediğinde başarısızlık modunu yükseltmeye değmez.
sh1

4
+1. Doğru uygulandığında, rastgele eşit olarak seçilen bir anahtar ile güvenli bir blok şifreleme kullanılarak, bu yöntem kullanılarak oluşturulan sekanslar, gerçek bir rastgele karıştırmadan hesaplanamaz şekilde ayırt edilebilir olacaktır. Yani, bu yöntemin çıktısını gerçek bir rastgele karıştırmadan, tüm olası blok şifreleme anahtarlarını test ederek ve bunlardan herhangi birinin aynı çıktıyı üretip üretmediğini görmekten önemli ölçüde daha hızlı ayırt etmenin bir yolu yoktur. 128 bit anahtar alanına sahip bir şifre için, bu muhtemelen şu anda insanlık için mevcut olan hesaplama gücünün ötesindedir; 256-bit anahtarlarla, muhtemelen sonsuza kadar böyle kalacaktır.
Ilmari Karonen

7

0 ... 1000 gibi düşük sayılar için, tüm sayıları içeren bir liste oluşturmak ve bu listeyi karıştırmak basittir. Ancak, çizilecek sayı kümesi çok büyükse, başka bir zarif yol daha vardır: Bir anahtar ve kriptografik karma işlevi kullanarak sahte bir permütasyon oluşturabilirsiniz. Aşağıdaki C ++ - ish örneği sözde koduna bakın:

unsigned randperm(string key, unsigned bits, unsigned index) {
  unsigned half1 =  bits    / 2;
  unsigned half2 = (bits+1) / 2;
  unsigned mask1 = (1 << half1) - 1;
  unsigned mask2 = (1 << half2) - 1;
  for (int round=0; round<5; ++round) {
    unsigned temp = (index >> half1);
    temp = (temp << 4) + round;
    index ^= hash( key + "/" + int2str(temp) ) & mask1;
    index = ((index & mask2) << half1) | ((index >> half2) & mask1);
  }
  return index;
}

Burada, hashbir karakter dizesini muhtemelen büyük bir imzasız tamsayı ile eşleştiren bazı rasgele sahte rasgele bir işlevdir. İşlevrandperm , sabit bir anahtar varsayarak 0 ... pow (2, bit) -1 içindeki tüm sayıların permütasyonudur. Değişkeni değiştiren her adım indextersine çevrilebilir olduğundan bu yapıdan kaynaklanır . Bu bir Feistel şifresinden esinlenmiştir .


Stackoverflow.com/a/16097246/648265 ile aynıdır , diziler için rastgele aynıdır.
ivan_pozdeev

1
@ivan_pozdeev: Teoride, sonsuz bilgi işlem gücünü varsayarsak, evet. Bununla birlikte, hash()yukarıdaki kodda kullanıldığı gibi, güvenli bir sözde işlev olduğu varsayıldığında , bu yapının kanıtlayıcı bir şekilde (Luby & Rackoff, 1988), kapsamlı bir üründen çok daha az çaba kullanarak gerçek bir rastgele karıştırmadan ayırt edilemeyen bir sözde permütasyon vereceği varsayılmaktadır. anahtar uzunluğunda üstel olan tüm anahtar alanının aranması. Makul büyüklükteki tuşlar için bile (örneğin 128 bit), bu, Dünya'daki toplam bilgi işlem gücünün ötesindedir.
Ilmari Karonen

(BTW, sadece bu argümanı biraz daha titiz hale getirmek için, hash( key + "/" + int2str(temp) )yukarıdaki geçici yapıyı HMAC ile değiştirmeyi tercih ederim , bu da güvenliği temelde temel karma sıkıştırma işlevinin güvenliğine indirgenebilir. Ayrıca, HMAC kullanmak birisinin yanlışlıkla bu yapıyı güvenli olmayan bir kripto olmayan karma işleviyle kullanmaya çalışması daha az olasıdır.)
Ilmari Karonen

6

Burada açıklanan Xincrol algoritmamı kullanabilirsiniz:

http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html

Bu, diziler, listeler, permütasyonlar veya ağır CPU yükü olmadan rastgele ancak benzersiz sayılar üretmek için saf bir algoritmik yöntemdir.

Son sürüm ayrıca sayı aralığını ayarlamanıza izin verir, Örneğin, 0-1073741821 aralığında benzersiz rasgele sayılar istiyorsanız.

Pratikte kullandım

  • Her şarkıyı rastgele, ancak albüm / dizin başına yalnızca bir kez çalan MP3 çalar
  • Efekti çözen piksel hızlı video çerçeveleri (hızlı ve pürüzsüz)
  • İmzalar ve işaretçiler için görüntü üzerinde gizli bir "gürültü" sisi oluşturma (steganografi)
  • Veritabanları üzerinden çok sayıda Java nesnesinin serileştirilmesi için Veri Nesnesi Kimlikleri
  • Üçlü Çoğunluk bellek bitleri koruması
  • Adres + değer şifrelemesi (her bayt sadece şifreli değildir, aynı zamanda arabellekte yeni bir şifrelenmiş konuma da taşınır). Bu gerçekten kriptanaliz arkadaşlarını bana kızdırdı :-)
  • Düz Metin Crypt gibi Düz Metin SMS, e-postalar vb için Metin şifreleme.
  • Benim Texas Hold'em Poker Hesap Makinesi (THC)
  • Simülasyonlar, "karıştırma", sıralama için birkaç oyunum
  • Daha

Açık, ücretsiz. Bir şans ver...


Bu yöntem ondalık bir değer için işe yarayabilir mi, örneğin her zaman 3 basamaklı bir ondalık sonuca sahip olmak için 3 basamaklı bir ondalık sayacı karıştırmak mı?
Craig McQueen

Xorshift algoritmasının bir örneği olarak , ilgili tüm karışıklıkları olan bir LFSR'dir (örn. Dizidek birbirinden ayrı değerler daha fazla asla birlikte olamaz).
ivan_pozdeev

5

Bunu çözmek için bir diziye bile ihtiyacınız yok.

Bir bit maskesine ve bir sayaca ihtiyacınız var.

Sayacı sıfırlayın ve art arda yapılan aramalarda artırın. Sayacı bit maskesiyle (başlangıçta rasgele seçilir veya sabitlenir) XOR'a göre bir sayılan son sayı oluşturur. 1000'i aşan sayılarınız yoksa, 9 bit'ten daha geniş bir bit maskesi kullanmayın. (Başka bir deyişle, bit maskesi, 511'in üzerinde olmayan bir tamsayıdır.)

Sayaç 1000'i geçtiğinde sıfıra sıfırladığınızdan emin olun. Şu anda aynı sayı kümesini farklı bir sırayla üretmek için başka bir rastgele bitmask seçebilirsiniz - isterseniz.


2
Bu bir LFSR'den daha az insanı kandırır.
starblue

512 ... 1023 içindeki "bitmask" da sorun değil. Biraz daha sahte bir rastgelelik için cevabımı görün. :-)
sellibitze

Esasen stackoverflow.com/a/16097246/648265 ile eşdeğerdir , aynı zamanda sekanslar için rasgele başarısız olur.
ivan_pozdeev

4

Doğrusal uyumlu jeneratörün en basit çözüm olacağını düşünüyorum .

resim açıklamasını buraya girin

a , c ve m değerlerinde sadece 3 kısıtlama vardır

  1. m ve c nispeten asaldır,
  2. a-1 m'nin tüm asal faktörleri tarafından bölünebilir
  3. a-1 ile bölünebilen 4 ise m ile bölünebilen 4

PS yönteminden daha önce bahsedildi, ancak gönderinin sabit değerler hakkında yanlış varsayımları var. Aşağıdaki sabitler davanız için iyi çalışmalıdır

Durumda da kullanabilir a = 1002, c = 757,m = 1001

X = (1002 * X + 757) mod 1001

3

İşte ilk çözümün mantığını kullanan bazı kod. Ben bunun "dil agnostik" olduğunu biliyorum ama sadece herkes hızlı pratik bir çözüm arıyor durumunda bu C # bir örnek olarak sunmak istedim.

// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber];      // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber];     // Shuffled Array - not set

// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)                  
{
    OrderedArray[i] = i;
    listBox1.Items.Add(OrderedArray[i]);
}

// Execute the Shuffle                
for (int i = MaxNumber - 1; i > 0; i--)
{
    RandArrayNum = RandomClass.Next(i + 1);         // Save random #
    ShuffledArray[i] = OrderedArray[RandArrayNum];  // Populting the array in reverse
    LastNumInArray = OrderedArray[i];               // Save Last Number in Test array
    PickedNumInArray = OrderedArray[RandArrayNum];  // Save Picked Random #
    OrderedArray[i] = PickedNumInArray;             // The number is now moved to the back end
    OrderedArray[RandArrayNum] = LastNumInArray;    // The picked number is moved into position
}

for (int i = 0; i < MaxNumber; i++)                  
{
    listBox2.Items.Add(ShuffledArray[i]);
}

3

Bu yöntem, sınır yüksek olduğunda ve yalnızca birkaç rasgele sayı oluşturmak istediğinizde uygun olur .

#!/usr/bin/perl

($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)

$last = -1;
for $i (0 .. $n-1) {
    $range = $top - $n + $i - $last;
    $r = 1 - rand(1.0)**(1 / ($n - $i));
    $last += int($r * $range + 1);
    print "$last ($r)\n";
}

Sayıların artan sırada üretildiğini, ancak daha sonra karıştırabileceğinizi unutmayın.


Bu, permütasyonlar yerine kombinasyonlar ürettiğinden, stackoverflow.com/questions/2394246/…
ivan_pozdeev

1
Gösterir test bu daha düşük sayılar doğru bir eğilimi vardır: 2M örnekleri için ölçülen olasılıklar (top,n)=(100,10)vardır: (0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635). Python'da test ettim, matematikteki küçük farklılıklar burada bir rol oynayabilir (hesaplama için tüm işlemlerin rkayan nokta olduğundan emin oldum ).
ivan_pozdeev

Evet, bu yöntemin düzgün çalışması için, üst sınırın çıkartılacak değer sayısından çok daha büyük olması gerekir.
salva

"Üst sınır [değer sayısından çok daha büyük olsa bile ] " doğru "çalışmaz . Olasılıklar daha düşük bir marjla eşit olmayacaktır.
ivan_pozdeev

2

İyi bir sözde rasgele sayı üreteci kullanabilirsiniz10 bitli ve 1001 ila 1023'ü 0 ila 1000 bırakarak atabilirsiniz.

Dan burada biz 10 bitlik PRNG tasarımını olsun ..

  • 10 bit, geribildirim polinomu x ^ 10 + x ^ 7 + 1 (dönem 1023)

  • hızlı kod almak için Galois LFSR kullanın


@Phob Hayır olmayacak, çünkü Doğrusal Geri Besleme Kaydırma Kaydını temel alan 10 bitlik bir PRNG, genellikle ilk değere dönmeden önce (bir tanesi hariç) tüm değerleri bir kez kabul eden bir yapıdan yapılır. Başka bir deyişle, bir döngü sırasında tam olarak bir kez 1001 seçecektir.
Nuoji

1
@Bu sorunun asıl amacı her bir sayıyı bir kez seçmek. Ve sonra 1001'in arka arkaya iki kez olmayacağından şikayet mi ediyorsunuz? Optimum yayılımı olan bir LFSR, uzayındaki tüm sayıları sahte rasgele bir şekilde geçirir, ardından döngüyü yeniden başlatır. Başka bir deyişle, olağan rasgele bir işlev olarak kullanılmaz. Rastgele olarak kullanıldığında, tipik olarak sadece bitlerin bir alt kümesini kullanırız. Bu konuda biraz okuyun ve yakında mantıklı olacaktır.
Nuoji

1
Tek sorun, belirli bir LFSR'nin sadece bir sekansa sahip olmasıdır, böylece toplanan sayılar arasında güçlü bir korelasyon verir - özellikle, olası her kombinasyonu oluşturmaz.
ivan_pozdeev

2
public static int[] randN(int n, int min, int max)
{
    if (max <= min)
        throw new ArgumentException("Max need to be greater than Min");
    if (max - min < n)
        throw new ArgumentException("Range needs to be longer than N");

    var r = new Random();

    HashSet<int> set = new HashSet<int>();

    while (set.Count < n)
    {
        var i = r.Next(max - min) + min;
        if (!set.Contains(i))
            set.Add(i);
    }

    return set.ToArray();
}

N Yinelenmeyen rasgele sayılar gerektiğinde O (n) karmaşıklığına sahip olacaktır.
Not: İş parçacığı güvenliği uygulandığında rastgele statik olmalıdır.


O (n ^ 2), çünkü yeniden deneme sayısı ortalama olarak şimdiye kadar seçilen eleman sayısıyla orantılıdır.
ivan_pozdeev

Bir düşünün, min = 0 max = 10000000 ve N = 5 seçerseniz, kaç tane seçilirse seçilsin ~ = 0'ı yeniden dener. Ancak evet, max-min küçükse, o (N) parçalanır.
Erez Robinson

N << (max-min) ise o zaman hala orantılıdır, sadece katsayı çok küçüktür. Ve katsayılar asimptotik bir tahmin için önemli değil.
ivan_pozdeev

Bu O (n) değil. Her sette bu değer ve fazladan döngü bulunur.
paparazzo

2

Diyelim ki, O(n)tekrar karıştırmak için her başlattığınızda gecikme olmadan karışık listelere tekrar tekrar gitmek istiyorsunuz , bu durumda bunu yapabiliriz:

  1. 0 ile 1000 arası 2 liste A ve B oluşturun, 2nyer kaplar.

  2. Fisher-Yates kullanarak karışık liste A nzaman alır .

  3. Bir sayı çizerken, diğer listeye 1 adımlı Fisher-Yates shuffle yapın.

  4. İmleç liste sonunda olduğunda diğer listeye geçin.

preprocess

cursor = 0

selector = A
other    = B

shuffle(A)

Çizmek

temp = selector[cursor]

swap(other[cursor], other[random])

if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1

return temp

Bakmadan önce 2 liste tutmak veya bir listeyi tüketmek gerekli değildir . Fisher-Yates herhangi bir başlangıç ​​durumundan eşit olarak rastgele sonuçlar verir. Açıklama için stackoverflow.com/a/158742/648265 adresine bakın .
ivan_pozdeev

@ivan_pozdeev Evet, aynı sonuç, ama buradaki fikrim, karışıklığı çizim eyleminin bir parçası haline getirerek O (1) amortismana tabi tutmak.
Khaled.K

Sen anlamadın. Sen hiç listeyi sıfırlamak gerekmez tekrar karıştırma önce. Karıştırılıyor [1,3,4,5,2]karıştırma aynı sonucu üretecektir [1,2,3,4,5].
ivan_pozdeev

2

Soru verimli 0 arasındadır K tekrarlanmayan tamsayılar listesini ve bir üst bağlı N üretmek nasıl kopya olarak bağlanır - ve oluşturulan rastgele sayı başına O (1) olan bir şey istiyorsanız (O (n) olmadan) başlangıç ​​maliyeti)) kabul edilen cevapta basit bir değişiklik var.

Başlatılmamış bir harita oluşturmak yerine boş sıralanmamış bir harita oluşturun (boş bir sıralı harita öğe başına O (günlük k) alacaktır). Maksimum ise maks. 1000 olarak ayarlayın,

  1. R ile 0 ile maks. Arasında rastgele bir sayı seçin.
  2. Her iki harita öğesinin de r ve maks'in sırasız haritada bulunduğundan emin olun. Yoksa, dizinlerine eşit bir değerle oluşturun.
  3. R ve max öğelerini değiştir
  4. Öğe maks. Ve maksimum maks 1 azaltma (maks negatif olursa, işiniz tamamlanmıştır).
  5. 1. adıma geri dönün.

Başlatılmış bir dizinin kullanılmasıyla karşılaştırıldığında tek fark, öğelerin başlatılmasının ertelenmesi / atlanmasıdır - ancak aynı PRNG'den tam olarak aynı sayıları üretecektir.


1

Başka bir olasılık:

Bir dizi bayrak kullanabilirsiniz. Ve zaten seçili olduğunda bir sonrakini alın.

Ancak, 1000 aramadan sonra dikkat edin, fonksiyon asla sona ermeyecektir, bu yüzden bir koruma yapmanız gerekir.


Bu O (k ^ 2) 'dir, ortalama olarak şimdiye kadar seçilen değerlerin sayısıyla orantılı ek adımlarla.
ivan_pozdeev

1

Burada oynayabileceğiniz bazı örnek COBOL kodu.
Size RANDGEN.exe dosyasını gönderebilir, böylece istediğinizi görmek için onunla oynayabilirsiniz.

   IDENTIFICATION DIVISION.
   PROGRAM-ID.  RANDGEN as "ConsoleApplication2.RANDGEN".
   AUTHOR.  Myron D Denson.
   DATE-COMPILED.
  * ************************************************************** 
  *  SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
  *    ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
  *    DUPLICATIONS.  (CALL "RANDGEN" USING RANDGEN-AREA.)
  *     
  *  CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
  *    AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA     
  *
  *    FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE. 
  *    RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED 
  *    AND PASSED BACK TO YOU.
  *
  *  RULES TO USE RANDGEN:
  *
  *    RANDOM-NUMBERS-NEEDED > ZERO 
  *     
  *    COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
  *         
  *    RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
  *    WHEN COUNT-OF-ACCESSES IS ALSO = 0 
  *     
  *    RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
  *    (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)       
  *     
  *    YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
  *     THE FIRST TIME YOU USE RANDGEN.
  *     
  *    BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER > ZERO AND 
  *        RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
  *       
  *    YOU CAN LET RANDGEN BUILD A SEED FOR YOU
  *     
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER = ZERO AND 
  *        RANDOM-NUMBER-NEEDED > ZERO  
  *         
  *     TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
  *        A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
  *        RANDOM NUMBERS.
  *        COMPUTE LOW-RANGE =
  *             ((SECONDS * HOURS * MINUTES * MS) / 3).         
  *        A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
  *        AFTER RANDOM-NUMBER-BUILT IS CREATED 
  *        AND IS BETWEEN LOW AND HIGH RANGE
  *        RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
  *               
  * **************************************************************         
   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
   FILE-CONTROL.
   DATA DIVISION.
   FILE SECTION.
   WORKING-STORAGE SECTION.
   01  WORK-AREA.
       05  X2-POWER                     PIC 9      VALUE 2. 
       05  2X2                          PIC 9(12)  VALUE 2 COMP-3.
       05  RANDOM-NUMBER-BUILT          PIC 9(12)  COMP.
       05  FIRST-PART                   PIC 9(12)  COMP.
       05  WORKING-NUMBER               PIC 9(12)  COMP.
       05  LOW-RANGE                    PIC 9(12)  VALUE ZERO.
       05  HIGH-RANGE                   PIC 9(12)  VALUE ZERO.
       05  YOU-PROVIDE-SEED             PIC X      VALUE SPACE.
       05  RUN-AGAIN                    PIC X      VALUE SPACE.
       05  PAUSE-FOR-A-SECOND           PIC X      VALUE SPACE.   
   01  SEED-TIME.
       05  HOURS                        PIC 99.
       05  MINUTES                      PIC 99.
       05  SECONDS                      PIC 99.
       05  MS                           PIC 99. 
  *
  * LINKAGE SECTION.
  *  Not used during testing  
   01  RANDGEN-AREA.
       05  COUNT-OF-ACCESSES            PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBERS-NEEDED        PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBER                PIC 9(12) VALUE ZERO.
       05  RANDOM-MSG                   PIC X(60) VALUE SPACE.
  *    
  * PROCEDURE DIVISION USING RANDGEN-AREA.
  * Not used during testing 
  *  
   PROCEDURE DIVISION.
   100-RANDGEN-EDIT-HOUSEKEEPING.
       MOVE SPACE TO RANDOM-MSG. 
       IF RANDOM-NUMBERS-NEEDED = ZERO
         DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
         ACCEPT RANDOM-NUMBERS-NEEDED.
       IF RANDOM-NUMBERS-NEEDED NOT NUMERIC 
         MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF RANDOM-NUMBERS-NEEDED = ZERO
         MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES NOT NUMERIC
         MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
         MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
           TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
         DISPLAY 'DO YOU WANT TO PROVIDE SEED  Y OR N: '
           NO ADVANCING
           ACCEPT YOU-PROVIDE-SEED.  
       IF RANDOM-NUMBER = ZERO AND
          (YOU-PROVIDE-SEED = 'Y' OR 'y')
         DISPLAY 'ENTER SEED ' NO ADVANCING
         ACCEPT RANDOM-NUMBER. 
       IF RANDOM-NUMBER NOT NUMERIC
         MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
         GO TO 900-EXIT-RANDGEN.
   200-RANDGEN-DATA-HOUSEKEEPING.      
       MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
       IF COUNT-OF-ACCESSES = ZERO
         COMPUTE LOW-RANGE =
                ((SECONDS * HOURS * MINUTES * MS) / 3).
       COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.  
       COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
       MOVE X2-POWER TO 2X2.             
   300-SET-2X2-DIVISOR.
       IF 2X2 < (HIGH-RANGE + 1) 
          COMPUTE 2X2 = 2X2 * X2-POWER
           GO TO 300-SET-2X2-DIVISOR.    
  * *********************************************************         
  *  IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED.    *
  * ********************************************************* 
       IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
          COMPUTE RANDOM-NUMBER-BUILT =
                ((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
       IF COUNT-OF-ACCESSES = ZERO        
         DISPLAY 'SEED TIME ' SEED-TIME 
               ' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT 
               ' LOW-RANGE  ' LOW-RANGE.          
  * *********************************************     
  *    END OF BUILDING A SEED IF YOU WANTED TO  * 
  * *********************************************               
  * ***************************************************
  * THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT  *  
  * ***************************************************   
   400-RANDGEN-FORMULA.
       COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
       DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER 
         REMAINDER RANDOM-NUMBER-BUILT. 
       IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
          RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
         GO TO 600-RANDGEN-CLEANUP.
       GO TO 400-RANDGEN-FORMULA.
  * *********************************************     
  *    GOOD RANDOM NUMBER HAS BEEN BUILT        *               
  * *********************************************
   600-RANDGEN-CLEANUP.
       ADD 1 TO COUNT-OF-ACCESSES.
       COMPUTE RANDOM-NUMBER = 
            RANDOM-NUMBER-BUILT - LOW-RANGE. 
  * *******************************************************
  * THE NEXT 3 LINE OF CODE ARE FOR TESTING  ON CONSOLE   *  
  * *******************************************************
       DISPLAY RANDOM-NUMBER.
       IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
        GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.     
   900-EXIT-RANDGEN.
       IF RANDOM-MSG NOT = SPACE
        DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
        MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER. 
        MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
       DISPLAY 'RUN AGAIN Y OR N '
         NO ADVANCING.
       ACCEPT RUN-AGAIN.
       IF (RUN-AGAIN = 'Y' OR 'y')
         GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.
       ACCEPT PAUSE-FOR-A-SECOND.
       GOBACK.

1
Bunun gerçekten OP ihtiyaçlarını karşılayıp karşılayamayacağı hakkında hiçbir fikrim yok, ancak bir COBOL katkısı için sahne!
Mac

1

Buradaki cevapların çoğu, aynı sayıyı iki kez döndürmeyeceklerini garanti etmemektedir. İşte doğru bir çözüm:

int nrrand(void) {
  static int s = 1;
  static int start = -1;
  do {
    s = (s * 1103515245 + 12345) & 1023;
  } while (s >= 1001);
  if (start < 0) start = s;
  else if (s == start) abort();

  return s;
}

Kısıtlamanın iyi belirtildiğinden emin değilim. Biri, diğer 1000 çıkıştan sonra bir değerin tekrarlanmasına izin verildiğini varsayar, ancak bu, her ikisinin de 1000 setin sonunda ve başında göründüğü sürece 0'dan hemen sonra 0'ın takip etmesine izin verdiğini varsayar. Tekrarlar arasında 1000 diğer değer, bu işlem dizinin kendisini her seferinde aynı şekilde tekrarladığı bir durumu zorlar, çünkü bu sınırın dışında gerçekleşen başka bir değer yoktur.

Bir değer tekrarlanmadan önce her zaman en az 500 diğer değeri garanti eden bir yöntem:

int nrrand(void) {
  static int h[1001];
  static int n = -1;

  if (n < 0) {
    int s = 1;
    for (int i = 0; i < 1001; i++) {
      do {
        s = (s * 1103515245 + 12345) & 1023;
      } while (s >= 1001);
      /* If we used `i` rather than `s` then our early results would be poorly distributed. */
      h[i] = s;
    }
    n = 0;
  }

  int i = rand(500);
  if (i != 0) {
      i = (n + i) % 1001;
      int t = h[i];
      h[i] = h[n];
      h[n] = t;
  }
  i = h[n];
  n = (n + 1) % 1001;

  return i;
}

Bu, stackoverflow.com/a/196164/648265 gibi bir LCG'dir, sekanslar için rastgele olmayan ve aynı zamanda diğer ilgili bükülmeler.
ivan_pozdeev

@ivan_pozdeev benim bir LCG'den daha iyi çünkü 1001.
sh1

1

N 1000'den büyük olduğunda ve K rastgele örnekleri çizmeniz gerektiğinde, şimdiye kadar örnekleri içeren bir küme kullanabilirsiniz. Her çekiliş için ret örneklemesi kullanıyorsunuz için "neredeyse" O (1) işlemi olacak , böylece toplam çalışma süresi O (N) depolamayla neredeyse O (K) olur.

Bu algoritma K "yakın" N olduğunda çarpışmalara girer. Bu, çalışma süresinin O (K) 'den çok daha kötü olacağı anlamına gelir. Basit bir düzeltme, mantığı tersine çevirmektir, böylece K> N / 2 için henüz çizilmemiş tüm numunelerin kaydını tutarsınız. Her çizim, reddetme setinden bir örneği kaldırır.

Ret örneklemesi ile ilgili diğer bir sorun da, O'nun (N) depolanmasıdır, bu da N'nin milyarlarca veya daha fazla olması durumunda kötü bir haberdir. Ancak, bu sorunu çözen bir algoritma vardır. Bu algoritmaya mucit olduktan sonra Vitter'in algoritması denir. Algoritma burada açıklanmaktadır . Vitter algoritmasının özü, her çekilişten sonra, düzgün örneklemeyi garanti eden belirli bir dağılımı kullanarak rastgele bir atlama hesaplamanızdır.


Beyler, lütfen! Fisher-Yates yöntemi bozuk. İlkini 1 / N olasılıklı olanı ve ikincisini 1 / (N-1)! = 1 / N olanı seçersiniz. Bu taraflı bir örnekleme yöntemidir! Önyargıyı çözmek için gerçekten Vittter'in algoritmasına ihtiyacınız var.
Emanuel Landeholm

0

Fisher Yates

for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

Bu aslında O (n-1) çünkü son iki için sadece bir takas gerekiyor
Bu C #

public static List<int> FisherYates(int n)
{
    List<int> list = new List<int>(Enumerable.Range(0, n));
    Random rand = new Random();
    int swap;
    int temp;
    for (int i = n - 1; i > 0; i--)
    {
        swap = rand.Next(i + 1);  //.net rand is not inclusive
        if(swap != i)  // it can stay in place - if you force a move it is not a uniform shuffle
        {
            temp = list[i];
            list[i] = list[swap];
            list[swap] = temp;
        }
    }
    return list;
}

Bu konuda zaten bir cevap var ama oldukça uzun sargılı ve 1'de (0 değil)
durabileceğinizi


-1

Birisi "Excel'de rasgele sayılar yaratıyor" gönderdi. Bu ideali kullanıyorum. Str.index ve str.ran olmak üzere 2 bölümden oluşan bir yapı oluşturun; 10 rasgele sayı için 10 yapıdan oluşan bir dizi oluşturun. Str.index'i 0'dan 9'a ve str.ran'ı farklı rasgele sayıya ayarlayın.

for(i=0;i<10; ++i) {
      arr[i].index = i;
      arr[i].ran   = rand();
}

Diziyi arr [i] .ran içindeki değerlere göre sıralayın. Str.index şimdi rastgele bir sırada. Aşağıda c kodu:

#include <stdio.h>
#include <stdlib.h>

struct RanStr { int index; int ran;};
struct RanStr arr[10];

int sort_function(const void *a, const void *b);

int main(int argc, char *argv[])
{
   int cnt, i;

   //seed(125);

   for(i=0;i<10; ++i)
   {
      arr[i].ran   = rand();
      arr[i].index = i;
      printf("arr[%d] Initial Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   qsort( (void *)arr, 10, sizeof(arr[0]), sort_function);
   printf("\n===================\n");
   for(i=0;i<10; ++i)
   {
      printf("arr[%d] Random  Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   return 0;
}

int sort_function(const void *a, const void *b)
{
   struct RanStr *a1, *b1;

   a1=(struct RanStr *) a;
   b1=(struct RanStr *) b;

   return( a1->ran - b1->ran );
}
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.