Эффективный выбор случайных чисел - PullRequest
12 голосов
/ 26 марта 2010

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

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

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

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

Теперь, очевидно, это имеет плохое время выполнения в худшем случае, так как случайная функция в теории может добавить дублированные числа на вечность, таким образом оставаясь в цикле while навсегда. Однако числа выбираются из {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);
}

Ответы [ 8 ]

11 голосов
/ 26 марта 2010

Можно попробовать использовать существующую реализацию Java ( или эту ) для Mersenne Twister .

Имейте в виду, что большинство МТ не криптографически безопасны.

5 голосов
/ 26 марта 2010

Похоже, вы хотите выбрать комбинацию k - из набора 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);
    }
}
2 голосов
/ 26 марта 2010

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

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

2 голосов
/ 26 марта 2010

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

1 голос
/ 26 марта 2010

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

// 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);
}

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

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

1 голос
/ 26 марта 2010

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

0 голосов
/ 27 марта 2010

Не пытайтесь разработать свой известный генератор случайных чисел. Вместо этого используйте известный как SecureRandom:

http://www.owasp.org/index.php/Using_the_Java_Cryptographic_Extensions

0 голосов
/ 26 марта 2010

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...