Yinelenmeyen rastgele sayılar oluşturma


89

Bu durumda, MAX sadece 5'tir, bu yüzden kopyaları tek tek kontrol edebilirim, ancak bunu daha basit bir şekilde nasıl yapabilirim? Örneğin, MAX değeri 20 ise ne olur? Teşekkürler.

int MAX = 5;

for (i = 1 , i <= MAX; i++)
{
        drawNum[1] = (int)(Math.random()*MAX)+1;

        while (drawNum[2] == drawNum[1])
        {
             drawNum[2] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[3] == drawNum[1]) || (drawNum[3] == drawNum[2]) )
        {
             drawNum[3] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[4] == drawNum[1]) || (drawNum[4] == drawNum[2]) || (drawNum[4] == drawNum[3]) )
        {
             drawNum[4] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[5] == drawNum[1]) ||
               (drawNum[5] == drawNum[2]) ||
               (drawNum[5] == drawNum[3]) ||
               (drawNum[5] == drawNum[4]) )
        {
             drawNum[5] = (int)(Math.random()*MAX)+1;
        }

}

3
Çoğu (sözde) rasgele sayı üreteci, tam "döngüleri" için tekrar etmez. Sorun elbette, tam "döngülerinin" milyarlarca veya trilyonlarca değer olması ve ürettikleri değerlerin bu milyarlarca veya trilyonlarca değerden herhangi biri olabilmesidir. Teoride 5 veya 10'luk bir "çevrimi" ya da her neyse olan rastgele bir sayı üreteci üretebilirsiniz, ama muhtemelen değerinden daha fazla sorun.
Hot Licks

1
Ayrıca tekrar etmeyen bir rastgele sayı üreteci "daha az" rastlantısaldır: MAX = 5 ise ve 3 sayı okursanız, bir sonrakini% 50 olasılıkla tahmin edebilirsiniz, 4 sayı okursanız, bir sonrakini% 100 biliyorsunuz. Elbette!
icza



Yanıtlar:


151

En basit yol, olası sayıların bir listesini (1..20 veya her neyse) oluşturmak ve ardından bunları karıştırmaktır Collections.shuffle. O zaman istediğiniz kadar çok öğeyi alın. Bu, aralığınız sonunda ihtiyacınız olan öğe sayısına eşitse (örneğin, bir deste kartını karıştırmak için) harikadır.

Eğer 1..10.000 aralığında 10 rastgele eleman istiyorsanız (diyelim ki) bu pek işe yaramıyor - sonunda gereksiz yere çok fazla iş yaparsınız. Bu noktada, muhtemelen şimdiye kadar ürettiğiniz bir dizi değeri saklamak ve bir sonraki mevcut olmayana kadar bir döngüde sayılar oluşturmaya devam etmek muhtemelen daha iyidir:

if (max < numbersNeeded)
{
    throw new IllegalArgumentException("Can't ask for more numbers than are available");
}
Random rng = new Random(); // Ideally just create one instance globally
// Note: use LinkedHashSet to maintain insertion order
Set<Integer> generated = new LinkedHashSet<Integer>();
while (generated.size() < numbersNeeded)
{
    Integer next = rng.nextInt(max) + 1;
    // As we're adding to a set, this will automatically do a containment check
    generated.add(next);
}

Yine de set seçimine dikkat edin - LinkedHashSetburada önemsediğimiz ekleme sırasını koruduğu için çok kasıtlı olarak kullandım .

Bir diğer seçenek olan her zaman aralığını her zaman azaltılması ve mevcut değerlerin dengeleyerek, ilerleme. Örneğin, 0..9 aralığında 3 değer istediğinizi varsayalım. İlk yinelemede 0..9 aralığında herhangi bir sayı oluşturursunuz - diyelim ki 4 oluşturduğunuzu varsayalım.

İkinci yinelemede 0,8 aralığında bir sayı oluşturursunuz. Oluşturulan sayı 4'ten küçükse, olduğu gibi tutarsınız ... yoksa ona bir tane eklersiniz. Bu size 4 olmadan 0,9 sonuç aralığı verir. Bu şekilde 7 elde ettiğimizi varsayalım.

Üçüncü yinelemede, 0..7 aralığında bir sayı oluşturursunuz. Oluşturulan sayı 4'ten küçükse, olduğu gibi tutarsınız. 4 veya 5 ise, bir tane eklersiniz. 6 veya 7 ise, iki eklersiniz. Bu şekilde sonuç aralığı 4 veya 6 olmadan 0,9 olur.


Olası değerlerden oluşan bir dizi oluşturun, rastgele birini seçin (rastgele sayı modu dizi boyutu), seçilen sayıyı kaldırın (ve kaydedin), ardından tekrarlayın.
Hot Licks

Veya tam döngülü rastgele bir jeneratör kullanın (asal sayılara dayalı olanlar küçük asal sayılar kullanabilir - karşılık gelen küçük döngülerle) ve aralık dışı bırakma değerleri.
Paul de Vrieze

"Yine bir başka seçenek, her zaman ilerleme kaydetmektir" WAAAAY'in daha iyi bir çözümdür. Lütfen yansıtmak için düzenleyin. Ve bu harika cevap için teşekkür ederim.
user123321

1
@musselwhizzle: Yakında zaman bulmaya çalışacak. Yine de "WAAAY daha iyi" hakkında emin değilim - daha verimli olmasına rağmen "açıkça doğru" önemli ölçüde daha az olacak. Okunabilirlik uğruna performanstan ödün vermekten çoğu zaman mutlu oluyorum.
Jon Skeet

@Deepthi: OP'nin istediği maksimum değer - soruya göre.
Jon Skeet

19

İşte bunu nasıl yapacağım

import java.util.ArrayList;
import java.util.Random;

public class Test {
    public static void main(String[] args) {
        int size = 20;

        ArrayList<Integer> list = new ArrayList<Integer>(size);
        for(int i = 1; i <= size; i++) {
            list.add(i);
        }

        Random rand = new Random();
        while(list.size() > 0) {
            int index = rand.nextInt(list.size());
            System.out.println("Selected: "+list.remove(index));
        }
    }
}

Sayın Skeet'in belirttiği gibi:
Eğer n , seçmek istediğiniz rastgele seçilmiş sayıların sayısı ve N , seçim için mevcut olan sayıların toplam örnek alanı ise:

  1. Eğer n << N ise , sadece seçtiğiniz numaraları kaydetmeli ve seçilen numaranın içinde olup olmadığını görmek için bir listeyi kontrol etmelisiniz.
  2. Eğer n ~ = N ise , muhtemelen tüm örnek alanını içeren bir liste doldurarak ve ardından seçerken sayıları ondan kaldırarak benim yöntemimi kullanmalısınız.

liste bir LinkedList olmalıdır,
dizi

@RiccardoCasatta iddianız için bir kaynağınız var mı? Bağlantılı bir listeden geçmenin de çok başarılı olacağını hayal edemiyorum. Ayrıca bkz: stackoverflow.com/a/6103075/79450
Catchwa

Test ettim ve haklısın, yorumumu silmeli miyim?
Riccardo Casatta

@RiccardoCasatta Başkaları bizim ileri geri
aramamızı

13
//random numbers are 0,1,2,3 
ArrayList<Integer> numbers = new ArrayList<Integer>();   
Random randomGenerator = new Random();
while (numbers.size() < 4) {

    int random = randomGenerator .nextInt(4);
    if (!numbers.contains(random)) {
        numbers.add(random);
    }
}

Bu, büyük sayılar için korkunç bir performansa sahip olacaktır. ArrayList.contains, liste boyunca yineleniyor. Bunun yerine Set'e sahip olmak çok daha temiz olacaktır - içerip içermediğini kontrol etmenize gerek yoktur, sadece ekleme ve performans daha iyi olacaktır.
kfox

5

LFSR ile "rastgele" sıralı sayılar yapmanın başka bir yolu daha var, şuna bir göz atın:

http://en.wikipedia.org/wiki/Linear_feedback_shift_register

bu teknikle sıralı rastgele sayıyı indeksle elde edebilir ve değerlerin tekrarlanmamasını sağlayabilirsiniz.

Ancak bunlar DOĞRU rasgele sayılar değildir çünkü rasgele üretim deterministiktir.

Ancak durumunuza bağlı olarak, bu tekniği karıştırmayı kullanırken rastgele sayı oluşturmada işlem miktarını azaltarak kullanabilirsiniz.

İşte java'da bir LFSR algoritması (hatırlamadığım bir yere götürdüm):

public final class LFSR {
    private static final int M = 15;

    // hard-coded for 15-bits
    private static final int[] TAPS = {14, 15};

    private final boolean[] bits = new boolean[M + 1];

    public LFSR() {
        this((int)System.currentTimeMillis());
    }

    public LFSR(int seed) {
        for(int i = 0; i < M; i++) {
            bits[i] = (((1 << i) & seed) >>> i) == 1;
        }
    }

    /* generate a random int uniformly on the interval [-2^31 + 1, 2^31 - 1] */
    public short nextShort() {
        //printBits();

        // calculate the integer value from the registers
        short next = 0;
        for(int i = 0; i < M; i++) {
            next |= (bits[i] ? 1 : 0) << i;
        }

        // allow for zero without allowing for -2^31
        if (next < 0) next++;

        // calculate the last register from all the preceding
        bits[M] = false;
        for(int i = 0; i < TAPS.length; i++) {
            bits[M] ^= bits[M - TAPS[i]];
        }

        // shift all the registers
        for(int i = 0; i < M; i++) {
            bits[i] = bits[i + 1];
        }

        return next;
    }

    /** returns random double uniformly over [0, 1) */
    public double nextDouble() {
        return ((nextShort() / (Integer.MAX_VALUE + 1.0)) + 1.0) / 2.0;
    }

    /** returns random boolean */
    public boolean nextBoolean() {
        return nextShort() >= 0;
    }

    public void printBits() {
        System.out.print(bits[M] ? 1 : 0);
        System.out.print(" -> ");
        for(int i = M - 1; i >= 0; i--) {
            System.out.print(bits[i] ? 1 : 0);
        }
        System.out.println();
    }


    public static void main(String[] args) {
        LFSR rng = new LFSR();
        Vector<Short> vec = new Vector<Short>();
        for(int i = 0; i <= 32766; i++) {
            short next = rng.nextShort();
            // just testing/asserting to make 
            // sure the number doesn't repeat on a given list
            if (vec.contains(next))
                throw new RuntimeException("Index repeat: " + i);
            vec.add(next);
            System.out.println(next);
        }
    }
}

4

Eğer ile istediğiniz kaç tane sayı belirlemenizi sağlar başka bir yaklaşım sizeve minve maxiade sayıların değerleri

public static int getRandomInt(int min, int max) {
    Random random = new Random();

    return random.nextInt((max - min) + 1) + min;
}

public static ArrayList<Integer> getRandomNonRepeatingIntegers(int size, int min,
        int max) {
    ArrayList<Integer> numbers = new ArrayList<Integer>();

    while (numbers.size() < size) {
        int random = getRandomInt(min, max);

        if (!numbers.contains(random)) {
            numbers.add(random);
        }
    }

    return numbers;
}

Kullanmak için 0 ile 25 arasında 7 sayı döndürür.

    ArrayList<Integer> list = getRandomNonRepeatingIntegers(7, 0, 25);
    for (int i = 0; i < list.size(); i++) {
        System.out.println("" + list.get(i));
    }

4

Bu çok daha basit olurdu java-8:

Stream.generate(new Random()::ints)
            .distinct()
            .limit(16) // whatever limit you might need
            .toArray(Integer[]::new);

3

Tekrar etmeyen rastgele sayılara sahip olmanın en verimli ve temel yolu bu sözde kodla açıklanmaktadır. İç içe döngülere veya karma aramalara gerek yoktur:

// get 5 unique random numbers, possible values 0 - 19
// (assume desired number of selections < number of choices)

const int POOL_SIZE = 20;
const int VAL_COUNT = 5;

declare Array mapping[POOL_SIZE];
declare Array results[VAL_COUNT];

declare i int;
declare r int;
declare max_rand int;

// create mapping array
for (i=0; i<POOL_SIZE; i++) {
   mapping[i] = i;
}

max_rand = POOL_SIZE-1;  // start loop searching for maximum value (19)

for (i=0; i<VAL_COUNT; i++) {
    r = Random(0, max_rand); // get random number
    results[i] = mapping[r]; // grab number from map array
    mapping[r] = max_rand;  // place item past range at selected location

    max_rand = max_rand - 1;  // reduce random scope by 1
}

İlk yinelemenin başlamak için rastgele 3 sayısını oluşturduğunu varsayalım (0 - 19'dan). Bu, sonuçları [0] = eşleme [3], yani değeri 3 yapar. Daha sonra [3] 'ü 19'a eşlemeyi atardık.

Sonraki yinelemede rastgele sayı 5'ti (0-18 arası). Bu, sonuçları [1] = eşleme [5] yapar, yani değer 5 olur. Daha sonra [5] 'i 18'e atarız.

Şimdi bir sonraki yinelemenin tekrar 3'ü seçtiğini varsayalım (0-17 arasında). [2] sonuçlarına [3] eşleme değeri atanır, ancak şimdi bu değer 3 değil 19'dur.

Aynı koruma, arka arkaya 5 kez aynı sayıyı alsanız bile tüm sayılar için devam eder. Örneğin, rastgele sayı oluşturucu size arka arkaya beş kez 0 verirse, sonuçlar şöyle olur: [0, 19, 18, 17, 16].

Aynı numarayı asla iki kez alamazsınız.


Bunun sizin kulağa ne kadar rasgele geldiğini sanmıyorum. Standart rastgelelik testlerini geçiyor mu ?; spektrumun sonuna yakın sayıları yoğunlaştırıyor gibi görünüyor.
tucuxi

İşte temel durum. Havuz {a, b, c} 'dir. Yinelenmeyen 2 öğeye ihtiyacımız var. Aşağıdaki algoritma, çizebileceğimiz kombinasyonlar ve sonuçları: 0,0: a, c 0,1: a, b 1,0: b, a 1,1: b, c 2,0: c, a 2, 1: c, b Puan: a-4, b-4, c-4
blackcatweb

3

Bir dizinin tüm indekslerini oluşturmak genellikle kötü bir fikirdir, çünkü özellikle seçilecek sayıların oranı MAXdüşükse (karmaşıklık baskın hale gelirse O(MAX)) çok zaman alabilir. Bu, seçilecek sayıların oranı MAXbire yaklaşırsa daha da kötüleşir , çünkü seçilen endeksleri tüm dizilerden çıkarmak da pahalı hale gelir (yaklaşıyoruz O(MAX^2/2)). Ancak küçük sayılar için bu genellikle iyi çalışır ve özellikle hataya açık değildir.

Oluşturulan endeksleri bir koleksiyon kullanarak filtrelemek de kötü bir fikirdir, çünkü dizinleri diziye eklemek için biraz zaman harcanır ve aynı rasgele sayı birkaç kez çizilebildiği için ilerleme garanti edilmez (ancak yeterince büyükse MAXbu olası değildir. ). Bu karmaşıklığa yakın olabilir
O(k n log^2(n)/2), kopyaları göz ardı ederek ve koleksiyonun verimli arama için bir ağaç kullandığını varsayarsak (ancak kağaç düğümlerini ayırmanın önemli bir sabit maliyeti ve muhtemelen yeniden dengeleme zorunluluğu vardır ).

Diğer bir seçenek, rastgele değerleri en başından itibaren benzersiz bir şekilde oluşturarak ilerlemeyi garanti etmektir. Bu, ilk turda rastgele bir indeks [0, MAX]oluşturulduğu anlamına gelir :

items i0 i1 i2 i3 i4 i5 i6 (total 7 items)
idx 0       ^^             (index 2)

İkinci turda, yalnızca [0, MAX - 1]oluşturulur (zaten bir öğe seçildiği için):

items i0 i1    i3 i4 i5 i6 (total 6 items)
idx 1          ^^          (index 2 out of these 6, but 3 out of the original 7)

Endekslerin değerlerinin daha sonra ayarlanması gerekir: eğer ikinci indeks, dizinin ikinci yarısına denk gelirse (birinci indeksten sonra), boşluğu hesaba katmak için artırılması gerekir. Bunu, rastgele sayıda benzersiz öğe seçmemize izin veren bir döngü olarak uygulayabiliriz.

Kısa diziler için bu oldukça hızlı bir O(n^2/2)algoritmadır:

void RandomUniqueSequence(std::vector<int> &rand_num,
    const size_t n_select_num, const size_t n_item_num)
{
    assert(n_select_num <= n_item_num);

    rand_num.clear(); // !!

    // b1: 3187.000 msec (the fastest)
    // b2: 3734.000 msec
    for(size_t i = 0; i < n_select_num; ++ i) {
        int n = n_Rand(n_item_num - i - 1);
        // get a random number

        size_t n_where = i;
        for(size_t j = 0; j < i; ++ j) {
            if(n + j < rand_num[j]) {
                n_where = j;
                break;
            }
        }
        // see where it should be inserted

        rand_num.insert(rand_num.begin() + n_where, 1, n + n_where);
        // insert it in the list, maintain a sorted sequence
    }
    // tier 1 - use comparison with offset instead of increment
}

n_select_num5'in nerede ve n_number_numsenin MAX. n_Rand(x)İçinde döner rasgele tamsayılar [0, x](dahil). Bu, ekleme noktasını bulmak için ikili arama kullanılarak çok sayıda öğe seçildiğinde (örn. 5 değil 500) biraz daha hızlı yapılabilir. Bunu yapmak için, gereksinimleri karşıladığımızdan emin olmalıyız.

İle n + j < rand_num[j]aynı olan karşılaştırma ile ikili arama yapacağız
n < rand_num[j] - j. Bunun rand_num[j] - jhala sıralanmış bir dizi için sıralanmış bir dizi olduğunu göstermemiz gerekir rand_num[j]. Orijinalin iki öğesi arasındaki en düşük mesafe rand_numbir olduğundan (üretilen sayılar benzersizdir, bu nedenle her zaman en az 1 fark vardır), bu durum neyse ki kolayca gösterilebilir . Aynı zamanda, indisleri jtüm elemanlardan çıkarırsak, indeksteki
rand_num[j]farklar tam olarak 1'dir. Yani "en kötü" durumda, sabit bir sıra elde ederiz - ama asla azalmaz. İkili arama bu nedenle kullanılabilir ve O(n log(n))algoritma elde edilir :

struct TNeedle { // in the comparison operator we need to make clear which argument is the needle and which is already in the list; we do that using the type system.
    int n;

    TNeedle(int _n)
        :n(_n)
    {}
};

class CCompareWithOffset { // custom comparison "n < rand_num[j] - j"
protected:
    std::vector<int>::iterator m_p_begin_it;

public:
    CCompareWithOffset(std::vector<int>::iterator p_begin_it)
        :m_p_begin_it(p_begin_it)
    {}

    bool operator ()(const int &r_value, TNeedle n) const
    {
        size_t n_index = &r_value - &*m_p_begin_it;
        // calculate index in the array

        return r_value < n.n + n_index; // or r_value - n_index < n.n
    }

    bool operator ()(TNeedle n, const int &r_value) const
    {
        size_t n_index = &r_value - &*m_p_begin_it;
        // calculate index in the array

        return n.n + n_index < r_value; // or n.n < r_value - n_index
    }
};

Ve sonunda:

void RandomUniqueSequence(std::vector<int> &rand_num,
    const size_t n_select_num, const size_t n_item_num)
{
    assert(n_select_num <= n_item_num);

    rand_num.clear(); // !!

    // b1: 3578.000 msec
    // b2: 1703.000 msec (the fastest)
    for(size_t i = 0; i < n_select_num; ++ i) {
        int n = n_Rand(n_item_num - i - 1);
        // get a random number

        std::vector<int>::iterator p_where_it = std::upper_bound(rand_num.begin(), rand_num.end(),
            TNeedle(n), CCompareWithOffset(rand_num.begin()));
        // see where it should be inserted

        rand_num.insert(p_where_it, 1, n + p_where_it - rand_num.begin());
        // insert it in the list, maintain a sorted sequence
    }
    // tier 4 - use binary search
}

Bunu üç kriterde test ettim. İlk olarak, 7 öğeden 3 sayı seçildi ve seçilen öğelerin histogramı 10.000 denemenin üzerinde toplandı:

4265 4229 4351 4267 4267 4364 4257

Bu, 7 maddenin her birinin yaklaşık olarak aynı sayıda seçildiğini ve algoritmanın neden olduğu belirgin bir önyargı olmadığını göstermektedir. Tüm diziler ayrıca doğruluk (içeriklerin benzersizliği) açısından da kontrol edildi.

İkinci kriter, 5000 maddeden 7 sayıyı seçmeyi içeriyordu. Algoritmanın çeşitli sürümlerinin süresi 10.000.000'den fazla çalıştırma biriktirildi. Sonuçlar koddaki yorumlarda olarak belirtilmiştir b1. Algoritmanın basit versiyonu biraz daha hızlıdır.

Üçüncü kriter, 5000 maddeden 700 sayıyı seçmeyi içeriyordu. Algoritmanın çeşitli sürümlerinin zamanı tekrar toplandı, bu sefer 10.000'den fazla çalıştırma. Sonuçlar koddaki yorumlarda olarak belirtilmiştir b2. Algoritmanın ikili arama versiyonu artık basit olandan iki kat daha hızlı.

İkinci yöntem, makinemde 75'ten fazla öğe seçmek için daha hızlı olmaya başlıyor (her iki algoritmanın karmaşıklığının öğelerin sayısına bağlı olmadığını unutmayın MAX).

Yukarıdaki algoritmaların rastgele sayıları artan sırada ürettiğinden bahsetmeye değer. Ancak, sayıların oluşturulma sırasına göre kaydedileceği başka bir dizi eklemek ve bunun yerine (göz ardı edilebilir ek maliyetle O(n)) geri dönmek basit olacaktır . Çıktıyı karıştırmak gerekli değildir: bu çok daha yavaş olacaktır.

Kaynakların C ++ olduğuna dikkat edin, makinemde Java yok, ancak konsept açık olmalı.

DÜZENLE :

Eğlence için, tüm endeksleri içeren bir liste oluşturan
0 .. MAX, bunları rastgele seçen ve benzersizliği garanti etmek için listeden çıkaran yaklaşımı da uyguladım . Oldukça yüksek MAX(5000) seçtiğimden beri , performans felaket:

// b1: 519515.000 msec
// b2: 20312.000 msec
std::vector<int> all_numbers(n_item_num);
std::iota(all_numbers.begin(), all_numbers.end(), 0);
// generate all the numbers

for(size_t i = 0; i < n_number_num; ++ i) {
    assert(all_numbers.size() == n_item_num - i);
    int n = n_Rand(n_item_num - i - 1);
    // get a random number

    rand_num.push_back(all_numbers[n]); // put it in the output list
    all_numbers.erase(all_numbers.begin() + n); // erase it from the input
}
// generate random numbers

Ayrıca yaklaşımı set, aslında kıyaslamada ikinci sırada gelen b2ve ikili arama yaklaşımından sadece yaklaşık% 50 daha yavaş olan a (bir C ++ koleksiyonu) ile uyguladım . Bu anlaşılabilir bir durumdur, çünkü setekleme maliyetinin ikili aramaya benzer olduğu bir ikili ağaç kullanılır. Tek fark, ilerlemeyi yavaşlatan yinelenen öğeler elde etme şansıdır.

// b1: 20250.000 msec
// b2: 2296.000 msec
std::set<int> numbers;
while(numbers.size() < n_number_num)
    numbers.insert(n_Rand(n_item_num - 1)); // might have duplicates here
// generate unique random numbers

rand_num.resize(numbers.size());
std::copy(numbers.begin(), numbers.end(), rand_num.begin());
// copy the numbers from a set to a vector

Tam kaynak kodu burada .


2

Set arabirimini ( API ) uygulayan sınıflardan birini kullanabilir ve ardından oluşturduğunuz her sayıyı eklemek için Set.add () öğesini kullanabilirsiniz.

Dönüş değeri yanlışsa, sayının daha önce oluşturulduğunu bilirsiniz.


2

Tüm bunları yapmak yerine, bir LinkedHashSetnesne oluşturun ve Math.random()işleve göre rastgele sayılar oluşturun .... yinelenen herhangi bir giriş olursa, LinkedHashSetnesne bu sayıyı Listesine eklemeyecektir ... Bu Koleksiyon Sınıfında yinelenen değerlere izin verilmediğinden .. sonunda, yinelenen değerleri olmayan rastgele sayıların bir listesini alırsınız ....: D


2

Sorununuz, n elementten oluşan bir koleksiyondan rastgele k elementi seçecek gibi görünüyor. Collections.shuffle yanıtı bu nedenle doğrudur, ancak belirtildiği gibi verimsizdir: O (n).

Wikipedia: Fisher – Yates karıştırmasının , dizi zaten mevcut olduğunda bir O (k) sürümü vardır. Sizin durumunuzda, hiç eleman dizisi yoktur ve eleman dizisini yaratmak çok pahalı olabilir, örneğin max 20 yerine 10000000 olsaydı.

Karıştırma algoritması, her öğenin kendi dizinine eşit olduğu, n boyutunda bir dizinin başlatılmasını, bir aralıktaki her bir sayının bir önceki aralıktan daha küçük olan k rastgele sayının seçilmesini ve ardından öğeleri dizinin sonuna doğru değiştirilmesini içerir.

Aynı işlemi O (k) zamanında bir hashmap ile yapabilirsiniz, ancak bir çeşit acı olduğunu kabul etsem de. Bunun yalnızca k, n'den çok daha küçükse işe yarayacağını unutmayın. (yani k ~ lg (n) ya da öylesine), aksi takdirde karıştırmayı doğrudan kullanmalısınız.

Karma haritanızı, karıştırma algoritmasındaki yedekleme dizisinin verimli bir temsili olarak kullanacaksınız. Dizinin dizinine eşit olan herhangi bir öğesinin haritada görünmesi gerekmez. Bu, n boyutunda bir diziyi sabit zamanda temsil etmenizi sağlar, onu başlatmak için harcanan zaman yoktur.

  1. K rastgele sayıları seçin: ilki 0 - n-1, ikinci 0 - n-2, üçüncü 0 - n-3 ve nk arasında.

  2. Rastgele sayılarınıza bir dizi takas gibi davranın. İlk rastgele indeks, son konuma geçer. İkinci rasgele indeks, ikinciden son konuma geçer. Ancak, bir destek dizisine karşı çalışmak yerine, karma haritanıza karşı çalışın. Karma haritanız, konum dışı olan her öğeyi saklayacaktır.

int getValue(i) { if (map.contains(i)) return map[i]; return i; } void setValue(i, val) { if (i == val) map.remove(i); else map[i] = val; } int[] chooseK(int n, int k) { for (int i = 0; i < k; i++) { int randomIndex = nextRandom(0, n - i); //(n - i is exclusive) int desiredIndex = n-i-1; int valAtRandom = getValue(randomIndex); int valAtDesired = getValue(desiredIndex); setValue(desiredIndex, valAtRandom); setValue(randomIndex, valAtDesired); } int[] output = new int[k]; for (int i = 0; i < k; i++) { output[i] = (getValue(n-i-1)); } return output; }


creating the array of elements could be very expensive- neden bir dizi oluşturmak karıştırmaktan daha pahalı olsun? Bence bu noktada karamsarlık için kesinlikle bir neden yok :-)
Wolf

1

Aşağıdaki kod, daha önce üretilmemiş olan [1, m] arasında rastgele bir sıra numarası oluşturur.

public class NewClass {

    public List<Integer> keys = new ArrayList<Integer>();

    public int rand(int m) {
        int n = (int) (Math.random() * m + 1);
        if (!keys.contains(n)) {
            keys.add(n);
            return n;
        } else {
            return rand(m);
        }
    }

    public static void main(String[] args) {
        int m = 4;
        NewClass ne = new NewClass();
        for (int i = 0; i < 4; i++) {
            System.out.println(ne.rand(m));
        }
        System.out.println("list: " + ne.keys);
    }
}


0

Kart destesinin algoritması vardır: sıralı sayı dizisi yaratırsınız ("kart grubu") ve her yinelemede ondan rastgele konumda bir sayı seçersiniz (seçilen sayıyı "kart grubundan" elbette kaldırırsınız).


0

İşte rastgele bir dizinin hızlı oluşturulması için etkili bir çözüm. Rastgeleleştirmeden sonra, basitçe dizinin n-ci öğesini seçebilir, artırabilir ve geri dönebilirsiniz . Bu çözümde rastgele bir sayı elde etmek için O (1) ve başlatma için O (n) vardır, ancak değiş tokuş olarak n yeterince büyük olursa iyi miktarda bellek gerektirir.ene


0

Tamsayılar için Collections.shuffle'dan daha verimli ve daha az külfetli bir çözüm var.

Sorun, bir sette yalnızca seçilmemiş öğelerden art arda öğe seçmek ve bunları başka bir yerde sıralamakla aynıdır. Bu tam olarak rastgele kart dağıtmak veya bir şapka veya çöp kutusundan kazanan çekiliş biletleri çekmek gibidir.

Bu algoritma, herhangi bir diziyi yüklemek ve yükleme sonunda rastgele bir sıraya ulaşmak için çalışır. Ayrıca, bir List koleksiyonuna (veya herhangi bir indekslenmiş koleksiyona) ekleme ve eklemelerin sonunda koleksiyonda rastgele bir sıra elde etmek için çalışır.

Bir kez oluşturulan tek bir diziyle veya bir Liste gibi sayısal olarak sıralı bir koleksiyonla yerinde yapılabilir. Bir dizi için, ilk dizi boyutunun istenen tüm değerleri içerecek tam boyutta olması gerekir. Önceden kaç değerin ortaya çıkabileceğini bilmiyorsanız, boyutun değişmez olmadığı ArrayList veya List gibi sayısal olarak sıralı bir koleksiyon kullanmak da işe yarayacaktır. 2.000.000.000'in biraz üzerinde olan Tamsayı.MAX_VALUE'ye kadar her boyutta bir dizi için evrensel olarak çalışacaktır. Liste nesneleri aynı dizin sınırlarına sahip olacaktır. Bu boyutta bir diziye ulaşmadan önce makinenizin belleği bitebilir. Nesne türlerine yazılan bir diziyi yüklemek ve dizi yüklendikten sonra onu bir koleksiyona dönüştürmek daha verimli olabilir. Bu, özellikle hedef koleksiyon sayısal olarak indekslenmemişse geçerlidir.

Bu algoritma, aynen yazıldığı şekliyle, yinelemelerin olmadığı yerlerde çok eşit bir dağılım yaratacaktır. ÇOK ÖNEMLİ olan bir özellik, bir sonraki öğenin eklenmesinin mevcut boyut + 1'e kadar mümkün olması gerektiğidir. Böylece, ikinci öğe için konum 0'da veya konum 1'de saklamak mümkün olabilir. 20. öğe için 0'dan 19'a kadar herhangi bir yerde saklamak mümkün olabilir. İlk öğenin 0 konumunda kalması, başka herhangi bir konuma gitmesi kadar mümkündür. Bir sonraki yeni öğenin, bir sonraki yeni konum da dahil olmak üzere herhangi bir yere gitmesi mümkün olduğu kadar.

Dizinin rastgeleliği, rasgele sayı üretecinin rasgeleliği kadar rasgele olacaktır.

Bu algoritma, bir dizideki rastgele konumlara referans türlerini yüklemek için de kullanılabilir. Bu bir dizi ile çalıştığı için koleksiyonlarla da çalışabilir. Bu, koleksiyonu yaratmanız ve ardından karıştırmanız veya eklenen nesnelerin sırasına göre sipariş vermeniz gerekmediği anlamına gelir. Koleksiyonun yalnızca koleksiyonun herhangi bir yerine bir öğe ekleme veya ekleme yeteneğine sahip olması gerekir.

// RandomSequence.java
import java.util.Random;
public class RandomSequence {

    public static void main(String[] args) {
        // create an array of the size and type for which
        // you want a random sequence
        int[] randomSequence = new int[20];
        Random randomNumbers = new Random();

        for (int i = 0; i < randomSequence.length; i++ ) {
            if (i == 0) { // seed first entry in array with item 0
                randomSequence[i] = 0; 
            } else { // for all other items...
                // choose a random pointer to the segment of the
                // array already containing items
                int pointer = randomNumbers.nextInt(i + 1);
                randomSequence[i] = randomSequence[pointer]; 
                randomSequence[pointer] = i;
                // note that if pointer & i are equal
                // the new value will just go into location i and possibly stay there
                // this is VERY IMPORTANT to ensure the sequence is really random
                // and not biased
            } // end if...else
        } // end for
        for (int number: randomSequence) {
                System.out.printf("%2d ", number);
        } // end for
    } // end main
} // end class RandomSequence

0

Gerçekten her şey tam olarak ne için rastgele nesil ihtiyacınız olduğuna bağlıdır, ama işte benim düşüncem.

İlk olarak, rastgele sayıyı oluşturmak için bağımsız bir yöntem oluşturun. Sınırlara izin verdiğinizden emin olun.

public static int newRandom(int limit){
    return generatedRandom.nextInt(limit);  }

Sonra, değerleri karşılaştıran çok basit bir karar yapısı oluşturmak isteyeceksiniz. Bu, iki yoldan biriyle yapılabilir. Doğrulamak için çok sınırlı sayıda numaranız varsa, basit bir EĞER ifadesi yeterli olacaktır:

public static int testDuplicates(int int1, int int2, int int3, int int4, int int5){
    boolean loopFlag = true;
    while(loopFlag == true){
        if(int1 == int2 || int1 == int3 || int1 == int4 || int1 == int5 || int1 == 0){
            int1 = newRandom(75);
            loopFlag = true;    }
        else{
            loopFlag = false;   }}
    return int1;    }

Yukarıdakiler int1 ile int2'yi int5 arasında karşılaştırır ve rastgele sayılarda sıfır olmadığından emin olur.

Bu iki yöntem yerinde olduğunda aşağıdakileri yapabiliriz:

    num1 = newRandom(limit1);
    num2 = newRandom(limit1);
    num3 = newRandom(limit1);
    num4 = newRandom(limit1);
    num5 = newRandom(limit1);

Bunu takiben:

        num1 = testDuplicates(num1, num2, num3, num4, num5);
        num2 = testDuplicates(num2, num1, num3, num4, num5);
        num3 = testDuplicates(num3, num1, num2, num4, num5);
        num4 = testDuplicates(num4, num1, num2, num3, num5);
        num5 = testDuplicates(num5, num1, num2, num3, num5);

Doğrulamak için daha uzun bir listeniz varsa, daha karmaşık bir yöntem hem kodun netliği hem de kaynakların işlenmesi açısından daha iyi sonuçlar verecektir.

Bu yardımcı olur umarım. Bu site bana çok yardımcı oldu, ben de en azından denemek zorunda hissettim.


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.