2010-03-26 51 views
12

Tôi có một phương pháp, sử dụng các mẫu ngẫu nhiên để tính toán gần đúng. Phương pháp này được gọi là hàng triệu lần, do đó, điều rất quan trọng là quá trình chọn các số ngẫu nhiên là hiệu quả.Chọn các số ngẫu nhiên hiệu quả

Tôi không chắc chắn javas nhanh như thế nào Random().nextInt thực sự là như vậy, nhưng chương trình của tôi dường như không được hưởng lợi nhiều như tôi cũng muốn nó.

Khi chọn số ngẫu nhiên, tôi làm như sau (trong bán pseudo-code):

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

Bây giờ, điều này rõ ràng là có một xấu trường hợp xấu nhất thời gian chạy, vì ngẫu nhiên chức năng về mặt lý thuyết có thể thêm số trùng lặp cho một cõi đời đời, do đó sẽ tồn tại trong vòng lặp while mãi mãi. Tuy nhiên, các con số được chọn từ {0..45}, do đó, hầu như không thể có giá trị trùng lặp.

Khi tôi sử dụng phương pháp trên, chỉ 40% nhanh hơn phương pháp khác của tôi, phương pháp này không gần đúng, nhưng cho kết quả chính xác. Điều này được chạy ~ 1 triệu lần, vì vậy tôi đã mong đợi phương pháp mới này nhanh hơn ít nhất 50%.

Bạn có đề xuất nào về phương pháp nhanh hơn không? Hoặc có thể bạn biết một cách hiệu quả hơn để tạo ra một tập hợp các số ngẫu nhiên.

Để làm rõ, đây là hai phương pháp:

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

Sau một số thử nghiệm và hồ sơ, tôi thấy phương pháp này có hiệu quả nhất:

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

Bạn có thể nghỉ ngơi những gì bạn đang cố gắng hoàn thành không? Bạn đang cố gắng tạo một tập hợp N số riêng biệt với mỗi cuộc gọi phương thức? Bạn nói về việc so sánh phương pháp này với phương pháp khác "không xấp xỉ" và phương pháp khác nhanh hơn - là tạo số ngẫu nhiên thực sự hay phương pháp của bạn để thực hiện một số tính toán khác (xấp xỉ so với không xấp xỉ)? –

+0

Vấn đề là tạo số ngẫu nhiên. Các tính toán khác không liên quan, đó là lý do tại sao tôi không đề cập đến chúng trong câu hỏi của tôi. –

Trả lời

11

Bạn có thể thử sử dụng existing Java implementation (or this one) cho số Mersenne Twister.

Hãy nhớ rằng hầu hết MT là không phải là an toàn mã hóa.

+0

Bạn có thể làm rõ, ý bạn không phải là bảo mật bằng mật mã không? –

+1

Nó có nghĩa là bạn không nên sử dụng chúng cho mật mã, bởi vì nó vẫn có thể dự đoán số tiếp theo được đưa ra một lượng thông tin trước nhất định. – Tesserex

2

Một kỹ thuật phổ biến là bắt đầu với một danh sách của tất cả các yếu tố đầu vào có thể, và chọn ngẫu nhiên từ đó, xóa những thứ như bạn đi. Bằng cách đó không có nguy cơ chọn một bản sao và phải lặp lại trong một khoảng thời gian không xác định. Tất nhiên phương pháp này chỉ làm việc với dữ liệu rời rạc, nhưng may mắn là các số nguyên. Cũng nên nhớ rằng việc lựa chọn và xóa danh sách (hoặc cấu trúc dữ liệu khác) của bạn phải là O (1) nếu có thể, vì bạn đang tập trung vào tốc độ.

+0

Nếu ứng dụng giống như ứng dụng (máy tính tỷ lệ cược poker) thì có 52C5 == 2598960 đầu vào có thể, do đó, ít hơn 1/6 đầu vào sẽ được sử dụng. Đây là việc sử dụng bộ nhớ rất kém hiệu quả, vì các mẫu đầu vào (trong một máy tính tỷ lệ cược poker điển hình) không cần phải được giữ lại trong bộ nhớ sau khi đánh giá. Điều này sẽ còn tồi tệ hơn trong trường hợp có khả năng chức năng đánh giá được mở rộng đến tay 7 lá (52C7 == 133784560 kết hợp) – finnw

+0

có, bạn đúng - không may rằng thông tin bổ sung với các phương pháp thực tế không có trong câu hỏi khi tôi viết câu trả lời của tôi. – Tesserex

0

Đừng bao giờ đoán, luôn luôn đo lường.

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

Ngoài ra, bạn không bao giờ nên dựa vào mức độ thường xuyên của một lỗi đã biết sẽ xảy ra, chỉ cần mã defensivley để nó không xảy ra. Phát hiện một bản sao, và nếu nó là một bản sao sau đó không thêm nó, và bỏ qua các iteration với một tuyên bố continue.

Đối với các phương pháp nhanh nhất và số ngẫu nhiên ... Bạn không thể nhận được số ngẫu nhiên trong Java Math.random(). Bạn chỉ có thể nhận được số ngẫu nhiên giả. Làm thế nào nhanh chóng bạn muốn điều này được đi kèm với sự hy sinh của cách dường như ngẫu nhiên bạn cần cho họ xuất hiện. Cách nhanh nhất để tạo số giả ngẫu nhiên sẽ liên quan đến chuyển bit và bổ sung dựa trên giá trị hạt giống như System.getCurrentMilliSeconds() Ngoài ra, việc tạo số giả ngẫu nhiên đã khá nhanh vì nó chỉ là số liệu CPU thô, vì vậy có thể bạn sẽ đủ hạnh phúc khi bạn thấy có bao nhiêu mili giây để tạo một cái với Math.random().

+0

Không bao giờ * đo *. Luôn hồ sơ. –

+0

@Yuval không đo lường hồ sơ? –

+0

@Yuval: Nếu bạn không đo lường, bạn không biết khi nào nó đủ nhanh. Profiling thường xâm lấn. Bạn nên đo * và * profile ... mặc dù bạn chắc chắn không nên đo một cuộc gọi * đơn lẻ như thế này. –

2

Bạn có thể sử dụng tương đẳng tuyến tính như một máy phát điện ngẫu nhiên: http://en.wikipedia.org/wiki/Linear_congruential_generator [chưa xem xét nhược điểm thống kê của họ]

Bạn chỉ cần một tính (x + c)% m cho mỗi số. Tuy nhiên, theo kinh nghiệm của tôi, việc tạo ra các đối tượng (như bạn có thể làm với mọi cuộc gọi của Set mới và thêm, tùy thuộc vào việc bạn triển khai thực hiện) có thể khiến bạn tốn nhiều tốc độ hơn một cuộc gọi đến nextInt(). Có lẽ bạn nên thử một hồ sơ như ví dụ: cái này: http://www.eclipse.org/tptp/

+0

Tôi đang chạy os x, vì vậy tôi không thể sử dụng profiler nhật thực tptp! Tôi thực sự nhớ một hồ sơ! –

+0

Tôi đã từng sử dụng JProfiler trên Mac OS X. Afaik họ có thời gian dùng thử miễn phí là 14 ngày. – Searles

+0

Thx, tôi sẽ thử. –

1

Tôi không có bất kỳ đầu vào nào về vấn đề thực tế của bạn và tôi không biết quá nhiều Java (chỉ cần chọc xung quanh). Tuy nhiên có vẻ như với tôi rằng bạn đang cố gắng để xây dựng một bộ đánh giá tay cho poker và thread này http://pokerai.org/pf3/viewtopic.php?f=3&t=16 chứa một số đánh giá tay cực kỳ nhanh java. Hy vọng rằng một số mã này có thể giúp đỡ.

+0

Tôi đã được truyền cảm hứng từ một số thuật toán trong chuỗi này. Tôi đang thực hiện một omaha-evaluator mặc dù, rất nhiều trong số các công cụ trong chủ đề này, như bảng tra cứu đầu lên, tôi không thể sử dụng. –

5

Dường như bạn muốn chọn một k-combination từ một tập S mà không cần thay thế, với Sn giá trị khác biệt, k = 5 và n = 52. Bạn có thể shuffle() toàn bộ nhóm và chọn k yếu tố (như @Tesserex gợi ý) hoặc pick()k yếu tố trong khi tránh trùng lặp (như bạn đã hiển thị). Bạn sẽ muốn cấu hình cả trong môi trường cụ thể của bạn và cho máy phát điện đã chọn của bạn. Tôi thường xuyên, nhưng không phải lúc nào cũng thấy một cạnh khiêm tốn cho 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

Nếu bạn đang bị chậm lại bởi thực tế là bạn phải bỏ qua các bản sao, bạn có thể giải quyết vấn đề đó bằng cách tạo một danh sách tất cả các giá trị thẻ, và sau đó loại bỏ khỏi danh sách như thẻ được lựa chọn và chọn một số ngẫu nhiên trong phạm vi nhỏ hơn trong lần tiếp theo. Một cái gì đó như thế này:

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

Đối với lần rút đầu tiên, thẻ.get (n) sẽ trả lại n, bất kể n là gì. Nhưng từ đó trở đi, các giá trị sẽ bị xóa để thẻ.get (3) có thể trả về 7, v.v.

Tạo danh sách và xóa khỏi danh sách này sẽ thêm một loạt chi phí. Dự đoán của tôi là nếu bạn chỉ chọn 5 lá bài cùng một lúc, xác suất va chạm đủ nhỏ để loại bỏ hai mặt sau khi bạn tìm thấy chúng sẽ nhanh hơn ngăn cản chúng. Ngay cả trong trận hòa cuối cùng, xác suất của một bản sao chỉ là 4/52 = 1/13, vì vậy bạn hiếm khi đạt được một bản sao và xác suất mà 2 bản vẽ trong một hàng sẽ là bản sao sẽ nhỏ. Tất cả phụ thuộc vào thời gian cần thiết để tạo ra một số ngẫu nhiên so với thời gian cần thiết để thiết lập mảng và thực hiện việc xóa. Cách dễ nhất để nói sẽ là làm một số thí nghiệm và đo lường. (Hoặc hồ sơ!)

+0

Chính xác những gì tôi nghĩ - rằng xác suất của một bản sao quá nhỏ, thời gian cần thiết để ngăn chặn chúng mất nhiều thời gian hơn là chỉ kiểm tra chúng. Tôi đã cập nhật OP với kết quả của mình. –

Các vấn đề liên quan