In the first installment, we discovered, from the eyes of a noob, a handful of basic concepts about the Ruby language. In this episode, we’ll complete the functions for mean, median, variance and hence standard deviation and discover something you might consider magic…
The mean. It’s the average and so it’s calculation is simple. Add up all the elements and divide by the number of elements. The sum:
@sum = 0.0
@elements.each{ |i| @sum += i }
The mean:
def mean
if 0 == @elements.length
return 0
end
return @sum / @elements.length
end
Straightforward and fairly clean. The median is also pretty straightforward; you want the middle element, in the case of an odd-numbered quantity and the average of the middle elements in the case of an even-numbered quantity.
def median
if 0 == @elements.length
return 0
end
if 0 == (@elements.length % 2)
return even_median
else
return odd_median
end
end
I used the modulus operator to determine if the quantity of elements is odd or even, and it’s pretty much the same as almost every other language i’ve used. There is another way, using the remainder method:
any_even_number.remainder(2) is equal to 0
any_odd_number.remainder(2) is equal to 1
The even/odd_median methods just return the appropriate middle element. First the odd_median:
def odd_median
return @elements[(@elements.length/2).floor]
end
It might seem a little odd, at first, to not use the calculation (n+1)/2:
return @elements[(@elements.length + 1 )/2]
The reason we use the floor is because we know the number of elements divided by 2 will always yield a n.5 and we take the index down form that because we’re using a ZERO-based index in the array. (n+1)/2 works perfectly for a ONE-based index array. We could still use that approach however, but keeping in mind to subtract 1 from the result to get the correct index: ((n+1)/2) -1.
def even_median
top = @elements.length/2;
bottom = top -1
agg = @elements[bottom] + @elements[top]
return agg / 2.0
end
Again, because we’re working with ZERO-based index arrays, the calculations are slightly different to what you’d expect or do in “normal mathematics” (if there can ever be such a term?). Easy, peasy, japaneasy, right?
Variance is a slightly more complicated, although, not complex by any stretch of the imagination. There are two different algorithms for calculating the variance and in the provided code, i have implemented both. Armed with the knowledge of the equations and a decent grip on how to perform some calculations, the implementation is left to you as an exercise in applying your learning.
The magic. At last, the magic. (from Ruby-coloured glasses)
class Float
def prec(x)
mult = 10 ** x
return (self * mult).truncate.to_f / mult
end
end
The almost crazy thing happening here is that you have the ability to extend built-in types without much fuss. With this definition, you now have the prec method available to all your Floats. Try it in your console too.
irb(main):006:0> class Fixnum
irb(main):007:1> def plus10
irb(main):008:2> return self + 10
irb(main):009:2> end
irb(main):010:1> end
=> nil
irb(main):011:0> 43.plus10
=> 53
Now that’s cool. Strings, Times, Fixnums, Floats… If your domain requires a specific handling on a particular type; the ability to extend and make that appear “normal” for the remainder of the coding experience is easy. It just slides right in.
And that about wraps up Part II, save one small little detail you’ll find in the code but not elaborated just yet: the accessor. The Ruby User’s Guide has a great explanation, and now you also know about that website too!
The full source code for Learn By Do is available for download: sample_data.rb