Προγραμματιστική πρόκληση No 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 «Μου αρέσει»