What would be a 'ruby' way to do the following; I'm still thinking in more imperative style programming and not really adapting to thinking in ruby. What I want to do is find the closest element in size to the average of an array, for example, consider the following array
[1,2,3]
The average is 2.0. The method I want to write returns the element closest to the average from above and below it, in this case 1 and 3.
Another example will illustrate this better:
[10,20,50,33,22] avg is 27.0 method would return 22 and 33.
This is not the most efficient, but it is (in my humble opinion) rather Ruby-esque.
class Array
# Return the single element in the array closest to the average value
def closest_to_average
avg = inject(0.0,:+) / length
min_by{ |v| (v-avg).abs }
end
end
[1,2,3].closest_to_average
#=> 2
[10,20,50,33,22].closest_to_average
#=> 22
If you really want the n closest items, then:
class Array
# Return a number of elements in the array closest to the average value
def closest_to_average(results=1)
avg = inject(0.0,:+) / length
sort_by{ |v| (v-avg).abs }[0,results]
end
end
[10,20,50,33,22].closest_to_average #=> [22]
[10,20,50,33,22].closest_to_average(2) #=> [22, 33]
[10,20,50,33,22].closest_to_average(3) #=> [22, 33, 20]
avg = inject(0.0,:+) / length
is shorthand for:avg = self.inject(0.0){ |sum,n| sum+n } / self.length
I start off with a value of 0.0
instead of 0
to ensure that the sum will be a floating point number, so that dividing by the length does not give me an integer-rounded value.
sort_by{ |v| (v-avg).abs }
sorts the array based on the difference between the number and average (lowest to highest), and then:[0,results]
selects the first results number of entries from that array.
I assume that what is desired is the largest element of the array that is smaller than the average and the smallest value of the array that is larger than the average. Such values exist if and only if the array has at least two elements and they are not all the same. Assuming that condition applies, we need only convert it from words to symbols:
avg = a.reduce(:+)/a.size.to_f
[ a.select { |e| e < avg }.max, a.select { |e| e > avg }.min ]
Another way, somewhat less efficient:
avg = a.reduce(:+)/a.size.to_f
b = (a + [avg]).uniq.sort
i = b.index(avg)
[ b[i-1], b[i+1] ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With