Let's continue with our Dart-throwing example, that we solved in the last exercise. Now we sort of move towards the Generic programming.
Here are some solutions from the last exercise, so we have the following score function,
function score(x, y; outer = 10, middle = 5, inner = 1) z = √(x^2 + y^2) if 0 < inner < middle < outer if z ≤ inner return 10 elseif z ≤ middle return 5 elseif z ≤ outer return 1 else return 0 end else begin throw(ArgumentError("argument must be nonnegative and follow inner < middle < outer")) end end end
Then using the following code you could generate one data point from (this only generate one data point! in particular randpoint_uniform(10)
won't work, but this is fine for us now!)
randpoint_uniform() = 20*rand() - 10
Which in theory means, if we have , then if we scale it by 20 and then also shift it by , this gives us a new random variable , where . So in this case . Bottomline, we can generate random points between the interval -10 to 10 which are equally likely to be appeared, we call this function randpoint_uniform()
.
Question 3 of the last exercise asked to you to generate points specifically from this distribution. Let's say you can generate 100 random points, and then calculate scores for each of these hundred points, store all the points in a vector and then store all the scores in a vector. Plot the points on the dart picture and then plot the scores as a histogram, and that's it. I apologize because the question was not so clear. Here is the function which will do the job for you
function scores_of_all_throws(number_of_throws) all_points = zeros(2, number_of_throws) ## pre-allocate a zero vector for i = 1:number_of_throws ## we are looping over the columns all_points[:, i] .= randpoint_uniform(), randpoint_uniform() ## why not array?? [ ] end scores = score.(all_points[1, :], all_points[2, :]) ## broadcast, generate two points return scores, all_points end
Because the function returns two objects we can also save the return in two objects
scores, points = scores_of_all_throws(1000)
You can also check with @btime
from BenchmarkTools
package to see the performance (time + allocation) and then also check with @code_warntype
to check the type-stability.
Ok now we come to task,
Define a generic function scores_of_all_throws(dist, number_of_throws)
where dist
is going to be either randpoint_uniform()
or randn()
where randn()
function will generate the a data point from a standard Normal distribution.
Now use the function scores_of_all_throws(dist, number_of_throws)
to generate the data for 1000 throws. And save the returns in two vectors, one for all scores and one for all points. Plot the points in a histogram.
Now we take the problem to a more Generic extension, so we will define our own custom types,
Define a immutable composite type DartboardThreeCircle
with fields outer
, middle
and inner
. The types of the fields should be Float64
. And then it should only instantiate if 0 < inner < middle < outer
otherwise it should throw an error argument must be nonnegative and follow inner < middle < outer. Check with an example whether you could successfully instantiate (No parameters necessary, however if you prefer you can surely use it!)
Define composite type DartThrowerUniform
with fields a
, and b
where these are essentially parameters of a Uniform distribution (for example for , these are a = -10, and b = 10 ). All the fields types should be Float64
.
Then also define DartThrowerNormal
with the parameters μ
and σ
, with a condition σ >0
(otherwise throw some error if σ ≤ 0
). For example, for standard Normal, we have and . All the fields types should be Float64
.
Define a function randpoint
with two methods specifically for two types in particular you need to define
randpoint(dt::DartThrowerUniform)
and also randpoint(dt::DartThrowerNormal)
. Now these two methods will generate a data point from a transformed distribution. Here is the answer for the Uniform, randpoint(dt::DartThrowerUniform) = 2*dt.a*rand() - dt.b, 2*dt.a*rand() - dt.b
. You need to figure out for normal using randn()
. For Normal you need to think about how you transform a standard Normal to Normal with any mean and standard deviation .
Instantiate DartThrowerUniform
and DartThrowerNormal
, for example dtu = DartThrowerUniform(10,10)
and
dtn = DartThrowerNormal(0,10)
and check whether your randpoint
function (both methods) are working on them.
Now let's make the score function a bit more general. So write a score function score(x, y, d::DartboardThreeCircle)
(this will add another method to the existing score function) which can handle different dartboard objects and can calculate the score correctly for any dartobject of any size we give (the score calculation rules are same as before.)
Now let's finally make the score function more general. So define scores_of_all_throws(dart_thrower, number_of_throws, dartboard)
, where dart_thrower
could be instantiated using either the type of DartThrowerUniform
or DartThrowerNormal
(so this is why generic), number_of_throws
is just how many times you want to calculate the scores. And dartboard
argument will take a instantiated DartboardThreeCircle
object, but in principle it could take any size of dartborad
.
Check whether your last function is working,
dtu = DartThrowerUniform(10,10) d1 = DartboardThreeCircle(10,5,1) scores, points = scores_of_all_throws(dtu, 10, d1)
Now do the plotting, plot the scores for 1000 throws in a histogram. Here are the specifications for the single variate distributions (of course when you draw it is going to be bivariate but since iid, knowing single variate is enough!)
and , and DartboardCircle(20, 7, 3)
and , and DartboardCircle(10, 5, 3)
and , and DartboardCircle(8, 3, 1)