У меня есть метод, который использует случайные выборки для приближения вычисления. Этот метод называют миллионами времен, таким образом, его очень важное, что процесс выбора случайных чисел эффективен.
Я не уверен как быстро 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);
}
Вы можете попробовать использовать существующую реализацию Java ( или эту ) для Mersenne Twister .
Имейте в виду, что большинство MT не криптографически безопасны.
Обычный метод - начать со списка всех возможных входов и произвольно выбирать из него, удаляя все по ходу. Таким образом, нет риска выбрать дубликат и выполнить цикл в течение неизвестного времени. Конечно, этот метод работает только с дискретными данными, но, к счастью, с целыми числами. Также помните, что выбор и удаление вашего списка (или другой структуры данных) должны быть O (1), если это возможно, поскольку вы фокусируетесь на скорости.
Вы можете использовать линейное сравнение в качестве генератора случайных чисел: http://en.wikipedia.org/wiki/Linear_congruential_generator [но учтите их статистические недостатки]
Вам нужно только вычислить (x + c)% m для каждого числа. Тем не менее, по моему опыту, создание объектов (как вы могли бы делать с каждым вызовом new Set и add, в зависимости от того, какую реализацию вы используете) может стоить вам больше скорости, чем вызов nextInt (). Возможно, вам стоит попробовать профилировщик, например, этот: http://www.eclipse.org/tptp/
У меня нет информации о вашей реальной проблеме, и я не слишком хорошо знаю Java (просто ковыряюсь). Однако мне кажется, что вы пытаетесь создать оценщик рук для покера, и эта ветка http://pokerai.org/pf3/viewtopic.php?f=3&t=16 содержит очень быструю java-руку. оценщики. Надеюсь, что часть этого кода может помочь.
Если вам мешает пропускать дубликаты, вы можете решить эту проблему, создав список всех значений карт. , а затем удаление из списка по мере выбора карточек и выбор случайного числа в меньшем диапазоне в следующий раз. Примерно так:
// 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. Но с этого момента значения будут удаляться, поэтому card.get (3) может возвращать 7 и т. Д.
Создание списка и удаление из него добавляет кучу накладных расходов. Я предполагаю, что если вы выбираете только 5 карт за раз, вероятность коллизий достаточно мала, и устранение дубликатов после того, как вы их найдете, будет быстрее, чем их предотвращение. Даже при последнем розыгрыше вероятность дублирования составляет всего 4/52 = 1/13, так что вы вообще редко попадете в дубликат, и вероятность того, что два розыгрыша подряд будут дубликатами, будет крошечной. Все зависит от того, сколько времени требуется для генерации случайного числа по сравнению с тем, сколько времени требуется для настройки массива и выполнения удаления. Самый простой способ узнать это - провести несколько экспериментов и измерить. (Или профиль!)
Похоже, вы хотите выбрать комбинацию k - из набора S без замены, с S , имеющим n различных значений, k = 5 и n = 52. Вы можете перемешать ()
весь набор и выберите элементы 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);
}
}
Не пытайтесь разработать свой известный генератор случайных чисел. Вместо этого используйте известный, например SecureRandom:
http://www.owasp.org/index.php/Using_the_Java_Cryptographic_Extensions
Никогда не угадывай, всегда измеряй.
long time = System.getCurrentMilliseconds();
Random().nextInt()
System.out.println(System.getCurrentMilliseconds() - time);
Кроме того, вы никогда не должны полагаться на то, насколько редко будет происходить известная ошибка, просто используйте защитный код, чтобы этого не произошло. Обнаружьте дубликат, и если это дубликат, не добавляйте его и пропустите итерацию с помощью оператора continue
.
Что касается самых быстрых методов и случайных чисел ...
Вы не можете получить случайные числа в Java Math.random ()
. Вы можете получить только псевдослучайные числа. Насколько быстро вы хотите, чтобы это происходило, зависит от того, насколько, казалось бы, случайным вам нужно, чтобы они появлялись. Самый быстрый способ сгенерировать псевдослучайное число - это сдвиг и сложение битов на основе начального значения, такого как System.getCurrentMilliSeconds ()
Кроме того, генерация псевдослучайных чисел уже выполняется довольно быстро, поскольку это просто в любом случае необработанная арифметика ЦП, так что вы, вероятно, будете достаточно счастливы, когда увидите, сколько миллисекунд требуется для генерации таковой с помощью Math.random ()
.