-
Notifications
You must be signed in to change notification settings - Fork 0
/
Q2.py
187 lines (156 loc) · 5.77 KB
/
Q2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
"""
111901030
Mayank Singla
Coding Assignment 1 - Q2
"""
# %%
import matplotlib.pyplot as plt
from random import random
def handleError(method):
"""
Decorator Factory function.
Returns a decorator that normally calls the method of a class by forwarding all its arguments to the method.
It surrounds the method calling in try-except block to handle errors gracefully.
"""
def decorator(ref, *args, **kwargs):
"""
Decorator function that surrounds the method of a class in try-except block and call the methods and handles error gracefully.
"""
try:
method(ref, *args, **kwargs)
except Exception as err:
print(type(err))
print(err)
return decorator
class Dice:
"""
Dice class as mentioned in the question
Attributes:
numSides: int
The number of sides of the dice
probDist: list[float]
The probability distribution of the sides of the dice
Methods:
_validateSides(numSides);
_validateProbdist(dist);
_computeCDFIntervals(self);
__init__(numSides);
__str__();
setProb(dist);
roll(n);
"""
def _validateSides(self, numSides):
"""
Validates the given number of sides from input
"""
if (not isinstance(numSides, int)) or (numSides <= 4):
raise Exception("Cannot construct the dice")
def _validateProbDist(self, dist):
"""
Validates the given probability distribution of the sides
"""
if (
len(dist) != self.numSides
): # We need to assign probability to each side of dice
raise Exception("Invalid probability distribution")
sumProb = 0
for i in dist:
sumProb += i
if i < 0: # All the Probabilites should be >= 0
raise Exception("Invalid probability distribution")
if round(sumProb) != 1: # Sum of probabilites should be 1
raise Exception("Invalid probability distribution")
def _computeCDFIntervals(self):
"""
Returns the list of intervals of the CDF of the probability distribution
"""
ans = []
sum = 0
# Appending the intervals to the answer list
for val in self.probDist:
prevSum = sum
sum += val
ans.append((prevSum, sum))
return ans
@handleError
def __init__(self, numSides=6):
"""
Constructor for the class Dice with default numSides as 6.
Generates default probability distribution with equal probabilities for all the sides.
"""
self._validateSides(numSides)
self.numSides = numSides
self.probDist = []
# Setting default probabilites for each side
for _ in range(self.numSides):
self.probDist.append(1 / self.numSides)
def __str__(self):
"""
String dunder method, so that the object of class Dice should be printable in the format specified
"""
val = "Dice with {numFaces} faces and probability distribution {{".format(
numFaces=self.numSides
)
# Appending probability of each side to the string
for i, num in enumerate(self.probDist):
val = val + str(num)
if i != len(self.probDist) - 1:
val = val + ", "
val = val + "}"
return val
@handleError
def setProb(self, dist):
"""
Validates and sets the given probability distribution for the sides of the dice
"""
self._validateProbDist(dist)
self.probDist = list(dist)
def roll(self, n: int):
"""
Simulate n throws of a dice and generates random number based on the sampling.
Displays a bar chart showing the expected and actual number of occurrences of each face when the dice is thrown n times.
"""
# Getting the CDF intervals
intervals = self._computeCDFIntervals()
# The expected number of occurrences of each face
expected = list(map(lambda x: n * x, self.probDist))
# Computing the actual number of occurrences of each face
actual = [0] * self.numSides
for _ in range(n):
U = random() # Generating a random number
# Finding in which CDF interval, U lies
for i, (l, r) in enumerate(intervals):
if l < U and U < r:
actual[i] += 1 # Incrementing the count of the found interval
break
# Set of points for the x-axis
xpoints = list(range(1, self.numSides + 1))
# Shifting the points to the left for shifting the bar graph
shiftLeftXpoints = list(map(lambda x: x - 0.2, xpoints))
# Shifting the points to the right for shifting the bar graph
shiftRightXpoints = list(map(lambda x: x + 0.2, xpoints))
# Giving labels and title to the plot
plt.title(
"Outcome of {n} throws of a {numSides}-faced dice".format(
n=n, numSides=self.numSides
)
)
plt.xlabel("Sides")
plt.ylabel("Occurrences")
# Plotting the bar graph for actual and expected occurrences in blue and red
plt.bar(shiftLeftXpoints, actual, width=0.4, color="b", label="Actual")
plt.bar(shiftRightXpoints, expected, width=0.4, color="r", label="Expected")
# Locating the legend box as expected in the question
plt.legend(bbox_to_anchor=(0.5, 1.20), loc="upper center", ncol=2)
# Displaying the graph
plt.show()
if __name__ == "__main__":
# Sample Test Case1
d = Dice(5)
d.setProb((0.1, 0.2, 0.3, 0.2, 0.2))
print(d)
d.roll(10000)
# Sample Test Case 2
d = Dice(8)
print(d)
d.roll(10000)