Προγραμματιστική πρόκληση No 3: Ζάρια

Λογικά επειδή με τη rand100, όταν κάνει πηλίκο με 6 στη συνέχεια, (το 6 δεν διαιρεί το 100 ακριβώς αφήνει πηλίκο 4 περιπου, έχουμε και κάτι +1 που με δυσκολεύουν να το σκεφτώ), άρα είναι πιο πιθανό να πάρουμε 1,2,3,4 από το ζάρι από ότι 5,6. Ενώ όταν έχω RAND_MAX μεγαλύτερο αυτό το φαινόμενο είναι μικρότερης σημασίας. Τώρα σκέφτομαι εξουδετέρωση…

edit θα απαντήσω αύριο αν δεν με προλάβει κάποιος, όταν κοιμάμαι το μυαλό λύνει τέτοια προβλήματα

1 «Μου αρέσει»

Για να εξουδετερώσω αυτό το φαινόμενο, το πιο λογικό μου φαίνεται να αλλάξω το 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:

george@george-A715-71G:~$ ./exe r
Calculation took 0.03s
1 --> 166301
2 --> 167015
3 --> 166448
4 --> 166665
5 --> 166879
6 --> 166692

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 όπως θα έπρεπε να είναι ένα κανονικό ζάρι :grinning:

  • Πες πως θέλεις να φτιάξεις τους αριθμούς 1 και 2 μπορείς; Βεβαίως. Θα ρίξεις το ζάρι και αν το αποτέλεσμα είναι μονός αριθμός θα πάρεις το 1 αλλιώς το 2. Θα είναι το αποτέλεσμα τίμιο;

  • Έστω τώρα πως θέλεις τους αριθμούς 1,2,3,4. Μπορείς; Βεβαίως! Αν βγάλει τους αριθμούς 5,6 θα το αγνοήσω εντελώς. Θα είναι το αποτέλεσμα τίμιο;

  • Έστω τώρα πως θέλεις να φτιάξεις τους αριθμούς από 1 έως 11. Μπορείς; Βεβαίως. Θα ρίξεις το ζάρι δυο φορές. Θα τα αθροίσεις και θα πάρεις τους αριθμούς από δυο έως 12. Θα είναι το αποτέλεσμα τίμιο;

  • Ας επιστρέψουμε στο παράδειγμα 2. Σε κώδικα θα γράφαμε κάτι σαν rand6() % 4 όπου η συνάρτηση rand6() είναι το ζάρι με πολύ μικρή τιμή για το RAND_MAX ίση με το 5 ή 6). Δίνει τώρα αυτός ο τύπος ένα τίμιο ζάρι;

Οπότε το ερώτημα που πρέπει να απαντηθεί πρώτα είναι πόσα τίμια ζάρια (και πως) μπορούν να φτιαχτούν από ένα τίμιο ζάρι με 6 πλευρές.

1 «Μου αρέσει»

Να παραθέσω κι εγώ την εργασία μου… :slight_smile: Dice

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()
2 «Μου αρέσει»

Καλώς ήρθες @ChristosStm στην παρέα μας.

1 «Μου αρέσει»

Καλώς σας βρήκα! Την καλησπέρα μου κι από εδώ! :slight_smile:

1 «Μου αρέσει»

Εγώ πάλι βλέπω ένα σχετικά τίμιο ζάρι όσο έχουμε αύξηση του αριθμού των ρίψεων

#!/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 τότε κάποια ζαριά θα ευνοηθεί.

1 «Μου αρέσει»

Και λόγω της μεγάλης τιμής του RANDMAX είναι αρκετά τίμιο.

Η πρόκληση όμως δεν είναι να φτιάξεις ένα τίμιο ζάρι με την χρήση της έτοιμης συνάρτησης roll=random.randint(1,6), αλλά να το φτιάξεις από το rand100. Σε κώδικα κάπως έτσι

temp = random.randint(1,100)
roll = .... temp ...

Με άλλα λόγια πες πως ΔΕΝ έχεις την randint έτοιμη και φτιαγμένη από κάποιον άλλον. Σου έχω δώσει εγώ μια άλλη, που όμως δίνει τιμές μέχρι το 100.

def rand100:
    return  random.randint(1,100)

και πουθενά αλλού στον κώδικα δεν θα πρέπει να υπάρχει άμεση κλίση της randint

Επομένως οτιδήποτε ΔΕΝ είναι μέχρι το 6 δεν μπαίνει στο dictionary και δεν προσμετράται και λογικά αυτό επιδρά στην “τιμιότητα” του ζαριού.

Αυτό ναι είναι όντως πρόκληση

σιγά την πρόκληση, ενδεικτικά:

def zari():
    while True:
        x = randint(1,100)
        if x < 7: return x

:P

3 «Μου αρέσει»

Και επιτέλους έχουμε την πρώτη λύση που απαντά στο πρόβλημα. Και δουλεύει. Βέβαια αν δούλευες κάπου και το παρουσίαζες αυτό σαν λύση θα έπρεπε κανονικά να σε μαστιγώσουν και να σε πετάξουν στους κροκόδειλους, αλλά μέχρι στιγμής έχουμε την καλύτερη απάντηση.

Είσαι βέβαιος όμως πως αυτό που θα προκύψει είναι τίμιο, η θα πρέπει να αναλάβουν και οι αρκούδες;

Το θέμα είναι να βγαίνουν τίμια τα ζάρια και όχι απλά να πάρουμε μια λύση. Το θέμα είναι ότι η 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}%")
2 «Μου αρέσει»

Αντιγράφω από εδώ

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, που είναι η πρόκληση θα έχεις ένα καλύτερο κριτήριο για να αποφασίσεις.

1 «Μου αρέσει»

Είστε ωραίοι! Μια επισήμανση μόνο, αν τρέξει το πρόγραμμα με λίγες ρίψεις και δεν τύχει να έρθει κάποιο νούμερο από τα πρώτα έξι, τότε χτυπάει error για μηδενικό παρονομαστή.

1 «Μου αρέσει»
#!/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
2 «Μου αρέσει»

Όντως η ταχύτητα είναι κάτσε καλά.

1 «Μου αρέσει»

Να δώσω και εγώ μια λύση

#include <iostream>
#include <random>
#include <vector>

class Dice {
public:
  explicit Dice(uint num_sides = 6u)
      : num_sides(num_sides), random_engine{std::random_device{}()},
        distribution{1, num_sides} {}
  uint operator()() { return distribution(random_engine); }
  uint mumSides() const { return num_sides; }

private:
  uint num_sides;
  std::default_random_engine random_engine;
  std::uniform_int_distribution<uint> distribution;
};

void printHistogram(const std::vector<int> &histogram) {
  for (int i = 0; i < histogram.size(); i++) {
    std::cout << "[" << i + 1 << "] :" << histogram[i] << "\n";
  }
}

template <class T>
void testDice(T &dice, uint numRounds = 10000u) {
  std::vector<int> histogram(dice.mumSides(), 0);
  auto rolls = numRounds * dice.mumSides();
  for (int i = 0; i < rolls; i++) {
    auto roll = dice();
    histogram[roll - 1]++;
  }
  printHistogram(histogram);
}

class Dice6 {
public:
  Dice6() : fairDice(100) {}
  uint operator()() {
    while(true) {
      auto roll = fairDice();
      if (roll < 6+1) {
        return roll;
      }
    }
    //return (fairDice() % 6) +1 ;
  }
  uint mumSides() const { return 6; }
private:
  Dice fairDice;
};

int main() {
  auto fairDice = Dice{10};
  auto dice6 = Dice6{};

//  testDice<Dice>(fairDice, 10000);
  testDice<Dice6>(dice6, 100000);
  return 0;
}

Αλλά στην λύση αυτή υπάρχουν προβλήματα

  • Αν θέλω να φτιάξω ένα ζάρι με που να δίνει \{1,2,3\} θα πετάξω πολλούς αριθμούς
  • Αν θέλω ένα ζάρι με 12 πλευρές (από το 'fairDice` με 10 πλευρές) δεν μπορώ.
  • Επίσης τα αποτελέσματα τα ελέγχουμε οπτικά. Υπάρχει κάποιος τρόπος να αντιστοιχίσουμε με ένα αριθμό το πόσο τίμιο ή όχι είναι ένα ζάρι;

Υπάρχει κάποιος καλύτερος τρόπος;

1 «Μου αρέσει»

5 posts were split to a new topic: Τυχαίοι αριθμοί και συσκευές παραγωγής εντροπίας

Σήμερα έπεσα πάνω σε αυτό:

2 «Μου αρέσει»