Strange Symphonies The best way to predict the future is to invent it

11Nov/061

Unexpected behavior of Ruby’s Range.min Range.max

Ruby is here on a rescue mission to save your life!

While doing some of the eGenting questions I utilized Ruby's Range class to describe, well a range of numbers.

Generally people do:

AZIMUTH_MIN = 0
AZIMUTH_MAX = 360
AZIMUTH_RANGE = AZIMUTH_MAX - AZIMUTH_MIN

My version:

AZIMUTH = 0..360

Which is great, saves time coding, easier to understand that its a Range and overall looks darn cool, you can also get to use the neat features the Range class provides. So I naively thought, to find the range I would do:

AZIMUTH_RANGE = AZIMUTH.max - AZIMUTH.min

Looks perfectly valid, and doesn't appear to have any errors.

And it did its job well. :) That is until I started using large numbers, really large numbers, such as 4,000,000, then my program slowed down...
Upon a little investigating, I found out the the Range class includes the Enumerable module (which obviously make sense).

The Enumerable module, implements the min/max method to find the smallest/largest value. In other scenarios this would be great, imagine finding the largest number in a an array. By the way, an Array also implements the Enumerable module. In Ruby it should be any class that includes the Comparable module and overwrites the <=> (compare) method.
Example: Creating an Array:

a = [5, 10, 3, 7]
a.max #=> 10
a.min #=> 3
(0..9).max #=> 9

Since max searches through each element to find the largest value, it had to iterate 4 million times, which isn't very efficient. Sadly, it seems very logical to us where the max value is at. Luckily Range#first and Range#last is by your side.

But Ruby is a meta programming language, which basically means Magic, and a whole lot of it.

Of course, I would not suggest using this, as your modifying one of Ruby's Core classes, which could potentially be dangerous when using other libraries. But thought I'd show some Ruby magic for fun, for people not used to Ruby.

So let's see what we can do...

Declare anywhere you like:

class Range
alias :dilapidated_max :max
alias :dilapidated_min :min
alias :max :last
alias :min :first
end

So if you do want to use the original min/max methods, they are still exist. Of course if you run ruby with the "-w" (verbose) flag, it'll complain that your overwriting methods. In that case you can use this: Yes, I've scrunched it up just to save that tiny bit more space, and it aesthetically looks better...somewhat.

class Range
alias :dilapidated_max :max
alias :dilapidated_min :min
def max; last; end
def min; first; end
end

And Bingo! Your problem has been solved! I hope you haven't been bitten by this awkward behavior.

Some more interesting things you could do...

class Range
# Return the range the number overs
# careful, it doesn't work with characters: ie 'a'..'c'
def range
exclude_end? ? (last - first.succ) : (last - first)
end

# Return a random number within the range
def rand
Kernel::rand(exclude_end? ? (last - first) : (last.succ - first)) + first
end
end

Related posts

Tags

Tagged as: Leave a comment
Comments (1) Trackbacks (0)
  1. Aizat, congrat! You’re very expert already in Ruby. I baru nak merangkak2.


Leave a comment


No trackbacks yet.