2010-03-26 132 views
12

我有一個方法,它使用隨機樣本近似計算。這種方法被稱爲數百萬次,所以選擇隨機數的過程非常重要。有效選擇隨機數

我不知道Java類的速度有多快Random().nextInt真的,但我的計劃似乎並沒有那麼多好處,我想這一點。

當選擇隨機數,我做了以下(處於半僞代碼):

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

現在,這顯然有不好的最壞情況下的運行時間,因爲在理論上隨機功能爲永恆添加重複的數字,從而永遠留在while循環中。但是,這些數字是從{0..45}中選擇的,因此重複值大多數情況下不太可能。

當我用上面的方法,比我的另一種方法,它不接近它的速度只有40%,但產生正確的結果。這是大約100萬次,所以我期待這種新方法至少快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); 
} 
+0

你能再說一遍是什麼,你要完成?你是否試圖用每個方法調用生成一組N個不同的數字?你談論的是將這種方法與另一個「不近似」而另一種方法更快 - 是真正的問題隨機數生成還是用於其他計算(近似與非近似)的方法? – 2010-03-26 13:32:47

+0

問題是隨機數字的產生。其他計算不相關,這就是爲什麼我沒有提到他們在我的問題。 – 2010-03-26 13:40:32

回答

11

對於Mersenne Twister,您可以嘗試使用existing Java implementationor this one)。

記住最MT的是加密安全。

+0

你能澄清一下,你的意思是不是加密安全嗎? – 2010-03-26 13:55:11

+1

這意味着你不應該將它們用於密碼學,因爲在給定一定數量的先前信息的情況下,仍然可以預測下一個數字。 – Tesserex 2010-03-26 13:58:32

2

的一種常用技術是開始所有可能的輸入的列表,並從中隨機選擇,隨時刪除。這樣就沒有選擇重複的風險,並且必須循環不知道的時間。當然這種方法只適用於離散數據,但幸運的是整數。另外請記住,如果可能的話,您的列表(或其他數據結構)的選擇和刪除應該是O(1),因爲您關注速度。

+0

如果應用程序看起來像(撲克機率計算器),那麼有52C5 == 2598960個可能的輸入,因此將使用少於1/6的輸入。這是非常低效的內存使用,因爲輸入樣本(在典型的撲克機率計算器中)在評估後不需要保留在內存中。在評估功能擴展到7張牌手(52C7 == 133784560組合) – finnw 2010-03-26 16:45:09

+0

的情況下,這可能會更糟糕。是的,您是對的 - 不幸的是,當我寫信時,實際方法的附加信息不存在我的答案。 – Tesserex 2010-03-26 17:49:57

0

永遠猜不到,經常測量。

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

此外,你永遠不應該依賴於已知的錯誤將不會發生,只是代碼defensivley所以它不會。檢測重複,如果重複,則不要添加它,並用continue語句跳過迭代。

至於最快的方法和隨機數... 你不能在Java的Math.random()得到隨機數。你只能得到僞隨機數。你想要這樣做的速度有多快會犧牲你看起來隨機的表現。產生僞隨機數最快的方法將涉及位移和基於種子值的加法,例如System.getCurrentMilliSeconds()。而且,僞隨機數的產生已經非常快,因爲它只是原始的CPU算術,所以你可能會一旦你看到用Math.random()產生一個毫秒就足夠快樂了。

+0

從不*測量*。始終配置。 – 2010-03-26 13:29:26

+0

@Yuval是不是測量? – 2010-03-26 13:30:51

+0

@Yuval:如果你不測量,你不知道什麼時候速度夠快。分析通常是侵入性的。你應該測量*和*個人資料...雖然你當然不應該測量一個這樣的單一電話。 – 2010-03-26 13:31:10

2

你可以使用線性同餘隨機發生器:http://en.wikipedia.org/wiki/Linear_congruential_generator [尚未考慮其統計缺點]

你只需要的(X + C)%M爲每個數的計算。然而,根據我的經驗,對象的創建(比如每次調用新的Set和添加,取決於您使用的實現)可能會比調用nextInt()更快。也許你應該試試像例如這一個:http://www.eclipse.org/tptp/

+0

我正在運行os x,所以我不能使用eclipse tptp分析器!我真的很想念個人資料! – 2010-03-26 14:00:24

+0

我曾經在Mac OS X上使用JProfiler。Afaik他們有14天的免費試用期。 – Searles 2010-03-26 14:12:38

+0

Thx,我會試一試。 – 2010-03-26 14:18:09

1

我沒有任何關於你的實際問題的任何意見,我不知道太多的Java(只是徘徊)。然而,在我看來,你正試圖建立一個撲克手評估器,這個線程http://pokerai.org/pf3/viewtopic.php?f=3&t=16包含一些非常快速的Java手評估器。希望這些代碼中的一些可能會有所幫助。

+0

實際上,我在本主題中受到了一些算法的啓發。儘管我正在實現一個omaha評估器,但是這個線程中的很多東西,比如單挑查找表,我都無法使用。 – 2010-03-26 14:53:22

5

看起來你要選擇一個ķ - combination從一組小號無需更換,具有ň不同的值,ķ = 5和ñ = 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); 
    } 
} 
1

如果你被這樣的事實,你必須跳過重複放緩,你可以通過創建所有的卡值的列表,然後從列表中刪除的卡都選擇解決這個問題並在下一次選擇較小範圍內的隨機數。類似這樣的:

// 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,所以你很少打一個副本,並且連續兩次都是重複的概率很小。這一切都取決於需要多長時間才能生成一個隨機數,而不是設置數組需要多長時間並執行刪除操作。最簡單的方法是做一些實驗和測量。 (或簡介!)

+0

準確地說,我認爲 - 重複的概率非常小,以至於防止它們花費的時間比檢查它們需要更長的時間。我用我的結果更新了OP。 – 2010-03-26 21:30:49