Λογικά επειδή με τη rand100, όταν κάνει πηλίκο με 6 στη συνέχεια, (το 6 δεν διαιρεί το 100 ακριβώς αφήνει πηλίκο 4 περιπου, έχουμε και κάτι +1 που με δυσκολεύουν να το σκεφτώ), άρα είναι πιο πιθανό να πάρουμε 1,2,3,4 από το ζάρι από ότι 5,6. Ενώ όταν έχω RAND_MAX μεγαλύτερο αυτό το φαινόμενο είναι μικρότερης σημασίας. Τώρα σκέφτομαι εξουδετέρωση…
edit θα απαντήσω αύριο αν δεν με προλάβει κάποιος, όταν κοιμάμαι το μυαλό λύνει τέτοια προβλήματα
Για να εξουδετερώσω αυτό το φαινόμενο, το πιο λογικό μου φαίνεται να αλλάξω το 100 σε ένα πολλαπλάσιο του 6, πχ 60 ώστε όταν κάνω % 6 τα 0,1,2,3,4,5 να τείνουν σε ίδιο ποσό.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ARRAY_SIZE 1000000
int rand100() {
return rand() % 60 + 1;
}
int roll() {
int value;
value = rand100() % 6 + 1;
return value;
}
void randomness_check() {
float clock_start, clock_end, time;
int random_array[ARRAY_SIZE];
unsigned long long int i, counter[6];
clock_start = clock();
//initialize counter
for(i = 0; i < 6; i++) {
counter[i] = 0;
}
//generate random numbers
for(i = 0; i < ARRAY_SIZE; i++) {
random_array[i] = roll();
}
//count them
for(i = 0; i < ARRAY_SIZE; i++) {
switch(random_array[i]) {
case 1: {
counter[0] = counter[0] + 1;
break;
}
case 2: {
counter[1] = counter[1] + 1;
break;
}
case 3: {
counter[2] = counter[2] + 1;
break;
}
case 4: {
counter[3] = counter[3] + 1;
break;
}
case 5: {
counter[4] = counter[4] + 1;
break;
}
case 6: {
counter[5] = counter[5] + 1;
break;
}
}
}
clock_end = clock();
time = clock_end - clock_start;
printf("Calculation took %.2fs\n", time / 1000000);
printf("1 --> %lld\n", counter[0]);
printf("2 --> %lld\n", counter[1]);
printf("3 --> %lld\n", counter[2]);
printf("4 --> %lld\n", counter[3]);
printf("5 --> %lld\n", counter[4]);
printf("6 --> %lld\n", counter[5]);
}
int main(int argc, char *argv[]) {
srand(time(NULL));
if (argc == 2) {
if (*argv[1] == 'r') {
randomness_check();
}
}
else {
printf("--> %d\n", roll());
}
return 0;
}
Τώρα από το 100 έχει σίγουρα λιγότερη απόκλιση, ίσως και από πριν βάλω την rand100:
edit: Αναγνωρίζοντας αυτό το πρόβλημα και αφού googlara, θα μπορούσαμε στη συνάρτηση rand100 να απορρίπτουμε εξαρχής τις τιμές από το RAND_MAX που προκαλούν άνιση κατανομή, αλλά έτσι έχω τροποποιήσει τη rand100 και ξεφεύγω από τις προδιαγραφές:
int rand100() {
int value;
while(1) {
value = rand();
if (value < RAND_MAX - RAND_MAX % 60) {
return value % 60;
}
}
}
Επανέρχομαι για διευκρινήσεις. Ας πούμε πως έχουμε ένα φυσικό ζάρι με αριθμούς από 1 έως 6, ή από 0 έως 5 όπως θα έπρεπε να είναι ένα κανονικό ζάρι
Πες πως θέλεις να φτιάξεις τους αριθμούς 1 και 2 μπορείς; Βεβαίως. Θα ρίξεις το ζάρι και αν το αποτέλεσμα είναι μονός αριθμός θα πάρεις το 1 αλλιώς το 2. Θα είναι το αποτέλεσμα τίμιο;
Έστω τώρα πως θέλεις τους αριθμούς 1,2,3,4. Μπορείς; Βεβαίως! Αν βγάλει τους αριθμούς 5,6 θα το αγνοήσω εντελώς. Θα είναι το αποτέλεσμα τίμιο;
Έστω τώρα πως θέλεις να φτιάξεις τους αριθμούς από 1 έως 11. Μπορείς; Βεβαίως. Θα ρίξεις το ζάρι δυο φορές. Θα τα αθροίσεις και θα πάρεις τους αριθμούς από δυο έως 12. Θα είναι το αποτέλεσμα τίμιο;
Ας επιστρέψουμε στο παράδειγμα 2. Σε κώδικα θα γράφαμε κάτι σαν rand6() % 4 όπου η συνάρτηση rand6() είναι το ζάρι με πολύ μικρή τιμή για το RAND_MAX ίση με το 5 ή 6). Δίνει τώρα αυτός ο τύπος ένα τίμιο ζάρι;
Οπότε το ερώτημα που πρέπει να απαντηθεί πρώτα είναι πόσα τίμια ζάρια (και πως) μπορούν να φτιαχτούν από ένα τίμιο ζάρι με 6 πλευρές.
import random
'''
Info: Roll a dice
Author: Christos Stm
Last modified: October 2019
'''
class Dice(object):
''' Δημιουργεί ένα αντικείμενο τύπου 'Ζάρι' και χειρίζεται την μέθοδο τυχαίας ρίψης και της τυχαίας κατανομής
αποτελεσμάτων εάν και εφόσον κληθούν...'''
the_dice_sides = {} # Λεξικό αποθήκευσης αποτελεσμάτων αν κληθεί η allocation_of_resaults
def __init__(self, sides=6):
self.sides = sides
# for i in range(1, self.sides+1):
# Dice.the_dice_sides[i]=0
def allocation_of_results(self, tries):
''' Μέθοδος που ελέγχει την ίση κατανομή των αποτελεσμάτων για n προσπάθειες'''
Dice.the_dice_sides = {} # Αδειάζω το λεξικό σε περίπτωση που έχει ξαναχρησιμοποιηθεί
for i in range(tries):
# Για κάθε μια απ' τις προσπάθειες που περνάμε ως όρισμα,
resault = self.roll() # παίρνουμε μια τυχαία ρίψη ζαριού
Dice.the_dice_sides[resault] = Dice.the_dice_sides.get(resault, 0) + 1 # Αποθήκευση του αποτελέσματος στο λεξικό
for i in sorted(Dice.the_dice_sides):
print(i, Dice.the_dice_sides[i])
def roll(self):
''' Μέθοδος τυχαίας ρίψης ζαριού '''
return random.randint(1, self.sides)
def __str__(self):
return str('Dice Side: ' + str(self.roll()))
def game():
while True:
start = input('Do you want to start?\nType (S)tart or (E)xit ~: ').strip()
if start.upper()[0] == 'E': break
elif start.upper()[0] == 'S':
while True:
play = input('\nIf do you want to play once, press Enter\nElse, type a number of tries\n'
'For "exit" type (E)xit~: ')
if not play:
while True:
sides = input('Dice Sides: ')
if sides.isdigit():
roll = Dice(int(sides)) # Δημιουργία και ρίψη ζαριού με n πλευρές
print(roll)
break
else: continue
elif play.upper()[0] == 'E': break
elif play.isdigit():
while True:
sides = input('Dice Sides: ')
if sides.isdigit():
roll = Dice(int(sides)) # Δημιουργία και ρίψη ζαριού με n πλευρές
number = int(play)
roll.allocation_of_results(number) # κλήση της μεθόδου allocation πάνω στο ζάρι
break
else: continue
else: continue
else: continue
if __name__=='__main__':game()
Εγώ πάλι βλέπω ένα σχετικά τίμιο ζάρι όσο έχουμε αύξηση του αριθμού των ρίψεων
#!/usr/bin/env python
import random
# Αρχικοποίηση ζαριού
rolls = {"1":0, "2":0, "3":0, "4":0, "5":0 ,"6":0}
# Ερώτηση για αριθμό ρίψεων
n=int(input("Δώσε αριθμό ρίψεων : "))
# Ρίψη ζαρίας
for i in range(1,n+1):
roll=random.randint(1,6)
rolls[str(roll)]+=1
# Στατιστικά ζαριάς
for k in range (1,7):
percentage=100*rolls[str(k)]/sum(rolls.values())
print("Το",k,"ήρθε",rolls[str(k)],f"φορές με πιθανότητα {percentage:.2f}","%")
Ας πούμε σε ένα μεγάλο αριθμό ρίψεων μου έβγαλε
Δώσε αριθμό ρίψεων : 1235435
Το 1 ήρθε 205859 φορές με πιθανότητα 16.66 %
Το 2 ήρθε 205667 φορές με πιθανότητα 16.65 %
Το 3 ήρθε 205835 φορές με πιθανότητα 16.66 %
Το 4 ήρθε 206116 φορές με πιθανότητα 16.68 %
Το 5 ήρθε 205240 φορές με πιθανότητα 16.61 %
Το 6 ήρθε 206718 φορές με πιθανότητα 16.73 %
Που είναι σχετικά fair. Επιπλέον πρέπει να συνυπολογισθεί το γεγονός ότι αν ο αριθμός ρίψεων δεν είναι ακριβώς διαιρούμενος με το 6 τότε κάποια ζαριά θα ευνοηθεί.
Και λόγω της μεγάλης τιμής του RANDMAX είναι αρκετά τίμιο.
Η πρόκληση όμως δεν είναι να φτιάξεις ένα τίμιο ζάρι με την χρήση της έτοιμης συνάρτησης roll=random.randint(1,6), αλλά να το φτιάξεις από το rand100. Σε κώδικα κάπως έτσι
temp = random.randint(1,100)
roll = .... temp ...
Με άλλα λόγια πες πως ΔΕΝ έχεις την randint έτοιμη και φτιαγμένη από κάποιον άλλον. Σου έχω δώσει εγώ μια άλλη, που όμως δίνει τιμές μέχρι το 100.
def rand100:
return random.randint(1,100)
και πουθενά αλλού στον κώδικα δεν θα πρέπει να υπάρχει άμεση κλίση της randint
Και επιτέλους έχουμε την πρώτη λύση που απαντά στο πρόβλημα. Και δουλεύει. Βέβαια αν δούλευες κάπου και το παρουσίαζες αυτό σαν λύση θα έπρεπε κανονικά να σε μαστιγώσουν και να σε πετάξουν στους κροκόδειλους, αλλά μέχρι στιγμής έχουμε την καλύτερη απάντηση.
Είσαι βέβαιος όμως πως αυτό που θα προκύψει είναι τίμιο, η θα πρέπει να αναλάβουν και οι αρκούδες;
Το θέμα είναι να βγαίνουν τίμια τα ζάρια και όχι απλά να πάρουμε μια λύση. Το θέμα είναι ότι η random είναι δίκαια από τη φύση της γεγονός που αποδεικνύεται και στα ποσοστά που εξάγονται με τον ακόλουθο κώδικα.
#!/usr/bin/env python
import random
# Αρχικοποίηση ζαριού
rolls = {"1":0, "2":0, "3":0, "4":0, "5":0 ,"6":0}
# Ερώτηση για αριθμό ρίψεων
n=int(input("Δώσε αριθμό ρίψεων : "))
# Ρίψη ζαρίας
for i in range(1,n+1):
roll=random.randint(1,100)
if roll in range(1,7):
rolls[str(roll)]+=1
# Στατιστικά ζαριάς
percent=0
for k in range (1,7):
percentage=100*rolls[str(k)]/sum(rolls.values())
print("Το",k,"ήρθε",rolls[str(k)],f"φορές με πιθανότητα {percentage:.2f}","%")
percent += percentage
print("\nΠραγματοποιήθηκαν",sum(rolls.values()),"επιτυχείς ρίψεις, σε ποσοστό",f"{sum(rolls.values())/n:.2f}%")
There are no guarantees as to the quality of the random sequence produced. In the past, some implementations of rand() have had serious shortcomings in the randomness, distribution and period of the sequence produced (in one well-known example, the low-order bit simply alternated between 1 and 0 between calls). rand() is not recommended for serious random-number generation needs
To POSIX έχει επίσης βγάλει μια μορφή της rand() σαν obsolete. Πηγή Η C++ την έχει αντικαταστατήσει με ένα πολύ πολύπλοκο σύστημα που όμως δίνει “καλούς” τυχαίους αριθμούς. Δεν μπορεί να την αφαιρέσει, μιας και πολλά προγράμματα εξαρτώνται από αυτήν, αλλά αντικατέστησε την std::random_shuffle που την χρησιμοποιεί με την std::shuffle. Και έχοντας υπόψην τον δισταγμό να αφαιρούνται πράγματα από την γλώσσα, αυτό μας λέει πολλά.
Κατά συνέπεια η χρήση της rand() μπορεί να είναι ή να μην είναι “δίκαια” καθώς εξαρτάτε απο την υλοποίηση. Σε παλιές μηχανές τύπου spectrum ή κακή φύση της ήταν ορατή στα παιγνίδια. Σήμερα το πρόβλημα το κρύβει η μεγάλη τιμή της RAND_MAX.
Η κουβέντα αυτή βέβαια μέχρι στιγμής αφορά την C και το POSIX και όχι γλώσσες όπως την Python, που όμως την χρησιμοποιούν κάτω από το καπάκι. Η Python μπορεί να έχει μια καλή μηχανή που να σου δίνει καλή κατανομή, μπορεί και όχι. Δεν έχω δει τον κώδικα.
Αλλά ας θεωρήσουμε πως είναι μια καλή συνάρτηση (οι διαφορές είναι μικρές). Η πρόκληση είναι να την θεωρήσεις σωστή και να την χρησιμοποιήσεις έμμεσα. Αν φτιάξεις ένα ζάρι με 6 πλευρές από ένα ζάρι με 10, που είναι η πρόκληση θα έχεις ένα καλύτερο κριτήριο για να αποφασίσεις.
Είστε ωραίοι! Μια επισήμανση μόνο, αν τρέξει το πρόγραμμα με λίγες ρίψεις και δεν τύχει να έρθει κάποιο νούμερο από τα πρώτα έξι, τότε χτυπάει error για μηδενικό παρονομαστή.
#!/usr/bin/env python
import random
# Αρχικοποίηση ζαριού
rolls = {"1":0, "2":0, "3":0, "4":0, "5":0 ,"6":0}
# Ερώτηση για αριθμό ρίψεων με αποκλεισμό λανθασμένων εισαγωγών
while True:
try:
n=int(input("Δώσε αριθμό ρίψεων : "))
if n>0:break
else:
print("Ο αριθμός πρέπει να είναι > 0")
continue
except ValueError:
print("Δεν είναι αριθμός, ξαναπροσπάθηστε")
# Ρίψη ζαρίας
for i in range(1,n+1):
roll=random.randint(1,100)
if roll in range(1,7):
rolls[str(roll)]+=1
# Στατιστικά ζαριάς
percent=0
for k in range (1,7):
if rolls[str(k)]!=0 :
if rolls[str(k)]==1:
times="ά"
else:times="ές"
percentage=100*rolls[str(k)]/sum(rolls.values())
print("Το",k,"ήρθε",rolls[str(k)],f"φορ{times} με πιθανότητα {percentage:.2f}","%")
percent += percentage
if sum(rolls.values()) == 0:
print("\nΔεν υπήρξαν επιτυχείς ρίψεις")
elif sum(rolls.values())==1:
print("\nΠραγματοποιήθηκε 1 επιτυχής ρίψη")
else:
print("\nΠραγματοποιήθηκαν", sum(rolls.values()), "επιτυχείς ρίψεις, σε ποσοστό", f"{100*sum(rolls.values()) / n:3.2f}%")
Έκανα ένα μικρό fine tuning για να εμφανίζεται και ενικός αριθμός στην περίπτωση που έχουμε μια ρίψη από κάθε αριθμό. Πάντως στατιστικά η τιμιότητα του ζαριού εξαρτάται από το στατιστικό δείγμα. Επομένως το ζάρι που θα το ρίξουμε λίγες φορές είναι λιγότερο τίμιο από ένα που θα το ρίξουμε σε ένα στατιστικά σημαντικό αριθμό ρίψεων.
Με βάση τις προηγούμενες απαντήσεις, υλοποίηση με υπορουτίνες σε **FORTRAN(f90)**. Απλά για να συγκρίνετε ταχύτητες σε μεγάλο αριθμό ρίψεων :
PROGRAM dice
IMPLICIT NONE
INTEGER :: times
INTEGER, DIMENSION(6):: zari
REAL :: pithanotita
CALL prompt(times)
CALL zeroes(zari)
CALL ripsi(times,zari)
CALL apotelesmata(zari,pithanotita)
END PROGRAM dice
SUBROUTINE prompt(times)
IMPLICIT NONE
INTEGER :: times
PRINT *, 'Poses fores na paixei to zari?:'
READ *, times
IF (times .LE. 0) THEN
PRINT*,"Akyro noumero, program terminated."
STOP
ELSE IF (times .GT. 0) THEN
RETURN
END IF
RETURN
END SUBROUTINE prompt
SUBROUTINE zeroes(zari)
IMPLICIT NONE
INTEGER :: i
INTEGER, DIMENSION(6) :: zari
DO i=1,6
zari(i)=0
END DO
RETURN
END SUBROUTINE zeroes
SUBROUTINE ripsi(times,zari)
IMPLICIT NONE
INTEGER :: times,i,x,seed
INTEGER, DIMENSION(6):: zari
REAL :: r
CALL SYSTEM_CLOCK(seed)
CALL SRAND(seed)
DO i=1,times
x = 1+floor(100*RAND())
IF (x .LT. 7) THEN
zari(x)=zari(x)+1
END IF
END DO
RETURN
END SUBROUTINE ripsi
SUBROUTINE apotelesmata(zari,pithanotita)
IMPLICIT NONE
INTEGER, DIMENSION(6):: zari
INTEGER :: i
REAL:: pithanotita
DO i=1,6
IF (sum(zari) .EQ. 0) THEN
PRINT*,"Kanenas arithmos den vgike"
RETURN
END IF
pithanotita=real(zari(i))/real(SUM(zari))*100
IF (zari(i) .EQ. 1) THEN
PRINT*,"To",i,"irthe",zari(i),"fora me pithanotita ",pithanotita,"%"
ELSE IF (zari(i) .EQ. 0) THEN
CONTINUE
ELSE
PRINT*,"To",i,"irthe",zari(i),"fores me pithanotita",pithanotita,"%"
END IF
END DO
RETURN
END SUBROUTINE apotelesmata