Эффективное определение случайных чисел

У меня есть метод, который использует случайные выборки для приближения вычисления. Этот метод называется миллионы раз, поэтому очень важно, чтобы процесс выбора случайных чисел был эффективным.

Я не уверен, насколько быстрыми javas Random().nextInt действительно, но моя программа, похоже, не приносит столько пользы, как мне бы хотелось.

При выборе случайных чисел я делаю следующее (в полу псевдокоде):

// Repeat this 300000 times
Set set = new Set();
while(set.length != 5)
    set.add(randomNumber(MIN,MAX));

Теперь у этого, очевидно, плохое время ожидания худшего случая, так как случайная функция в теории может добавлять повторяющиеся числа в течение вечности, оставаясь во время цикла навсегда. Однако числа выбираются из {0..45}, поэтому дублирующее значение в большинстве случаев маловероятно.

Когда я использую вышеуказанный метод, его только на 40% быстрее, чем мой другой метод, который не приближается, но дает правильный результат. Это пробегает ~ 1 миллион раз, поэтому я ожидал, что этот новый метод будет как минимум на 50% быстрее.

Есть ли у вас предложения по более быстрому методу? Или, может быть, вы знаете более эффективный способ генерации набора случайных чисел.

Чтобы уточнить, вот два метода:

// Run through all combinations (1 million). This takes 5 seconds
 for(int c1 = 0; c1 < deck.length; c1++){
    for(int c2 = c1+1; c2 < deck.length; c2++){
     for(int c3 = c2+1; c3 < deck.length; c3++){
        for(int c4 = c3+1; c4 < deck.length; c4++){
         for(int c5 = c4+1; c5 < deck.length; c5++){
             enumeration(hands, cards, deck, c1, c2, c3, c4, c5);
         }
            } 
      }     
   }
   }

// Approximate (300000 combinations). This takes 3 seconds
Random rand = new Random();
HashSet<Integer> set = new HashSet<Integer>();
int[] numbers = new int[5];
while(enumerations < 300000){
set.clear();
while(set.size() != 5){
    set.add(rand.nextInt(deck.length));
}
Iterator<Integer> i = set.iterator();
int n = 0;
while(i.hasNext()){
    numbers[n] = i.next();
    n++;
}

После некоторого тестирования и профилирования я нашел этот метод наиболее эффективным:

Random rand = new Random();
int[] numbers = new int[5];
ArrayList<Integer> list = new ArrayList<Integer>();
while(enumerations < 300000){
 while(list.size() != 5) {
     int i = rand.nextInt(deck.length);
        if(!list.contains(i)) list.add(i);
 }
 int index = 0;
 for(int i : list){ numbers[index] = i; index++; }
 enumeration(hands, cards, deck,numbers);
}

Ответ 2

Похоже, вы хотите выбрать k-combination из набора S без замены, причем S имеет n различных значений, k = 5 и n = 52. Вы можете shuffle() весь набор и выбрать k элементов (как предлагает @Tesserex), или pick() k элементов, избегая дубликатов (как вы показали). Вы хотите профилировать как в своей конкретной среде, так и для выбранного генератора. Я часто, но не всегда, вижу скромный край для pick().

private static final Random rnd = new Random();
private static final int N = 52;
private static final int K = 5;
private static final List<Integer> S = new ArrayList<Integer>(N);
static {
    for (int i = 0; i < N; i++) {
        S.add(i + 1);
    }
}
private final List<Integer> combination = new ArrayList<Integer>(K);

...

private void shuffle() {
    Collections.shuffle(S, rnd);
    combination.addAll(S.subList(0, K));
}

private void pick() {
    for (int i = 0; i < K; i++) {
        int v = 0;
        do {
            v = rnd.nextInt(N) + 1;
        } while (combination.contains(v));
        combination.add(v);
    }
}

Ответ 3

Общим методом является начало списка всех возможных входов и случайный выбор, удаляющий их по ходу. Таким образом, нет риска выбора дубликата и необходимости петли в течение неизвестного времени. Конечно, этот метод работает только с дискретными данными, но, к счастью, целые. Также помните, что выбор и удаление списка (или другой структуры данных) должны быть O (1), если это возможно, поскольку вы фокусируетесь на скорости.

Ответ 4

Вы можете использовать линейную конгруэнцию как случайный генератор: http://en.wikipedia.org/wiki/Linear_congruential_generator [все же учитывать их статистические недостатки]

Вам нужно всего лишь вычислить (x + c)% m для каждого числа. Тем не менее, по моему опыту, создание объектов (например, вы можете делать с каждым вызовом нового Set и add, в зависимости от того, какую реализацию вы используете) может стоить вам больше скорости, чем вызов nextInt(). Может быть, вы должны попробовать профилировщик, например, например. этот: http://www.eclipse.org/tptp/

Ответ 5

У меня нет каких-либо данных о вашей реальной проблеме, и я не знаю слишком много Java (просто ковыряюсь). Однако мне кажется, что вы пытаетесь создать оценщика рук для покера, и этот поток http://pokerai.org/pf3/viewtopic.php?f=3&t=16 содержит некоторые очень быстрые оценки рук java. Надеюсь, что некоторые из этого кода могут помочь.

Ответ 6

Если вы замедлились из-за того, что вам нужно пропустить дубликаты, вы можете решить эту проблему, создав список всех значений карты, а затем удалив из списка, когда будут выбраны карты, и выберите случайный число в меньшем диапазоне в следующий раз. Что-то вроде этого:

// Assuming we're just numbering all the cards 0 to 51. This could be more sophisticated, of course.
ArrayList cards=new ArrayList(52);
for (int x=0;x<52;++x)
  cards=new Integer(x);

Integer[] hand=new Integer[5];
for (int h=0;h<5;++h)
{
  // Pick a card from those remaining
  int n=random.nextInt(cards.size());
  hand[h]=cards.get(n);
  // Remove the picked card from the list
  cards.remove(n);
}

Для первого розыгрыша card.get(n) вернет n, независимо от того, что n. Но с этого момента значения будут удалены, поэтому cards.get(3) может вернуть 7 и т.д.

Создание списка и удаление из него добавляет кучу накладных расходов. Я предполагаю, что если вы только выбираете по 5 карт за раз, вероятность коллизий достаточно мала, что устранение дубликатов после того, как вы их найдете, будет быстрее, чем предотвращать их. Даже на последней ничьей вероятность дублирования равна только 4/52 = 1/13, поэтому вы редко попадаете в дубликат, и вероятность того, что 2 розыгрыша подряд будет одинаковой, будет дублирующей. Все зависит от того, сколько времени требуется для создания случайного числа по сравнению с тем, сколько времени требуется для настройки массива и удаления. Самый простой способ рассказать - это сделать некоторые эксперименты и измерить. (Или профиль!)

Ответ 7

Никогда не угадывайте, всегда измеряйте.

 long time = System.getCurrentMilliseconds();
 Random().nextInt()
 System.out.println(System.getCurrentMilliseconds() - time);

Кроме того, вы никогда не должны полагаться на то, как редко случается известная ошибка, просто защитите код, чтобы он этого не сделал. Обнаружение дубликата, а если это дубликат, не добавляйте его и пропустите итерацию с помощью инструкции continue.

Как для самых быстрых методов и случайных чисел... Вы не можете получить случайные числа в Java Math.random(). Вы можете получить только псевдослучайные числа. Как быстро вы хотите, чтобы это происходило при жертве того, как кажутся случайными, которые вам нужны для них. Самый быстрый способ генерации псевдослучайного числа включал бы смещение и добавление битов на основе начального значения, такого как System.getCurrentMilliSeconds(). Кроме того, генерация псевдослучайных чисел уже довольно быстрая, поскольку в любом случае это просто исходная арифметика процессора, поэтому вы будете вероятно, будет достаточно счастлив, как только вы увидите, сколько миллисекунд требуется для создания одного с Math.random().