This is loosely based on "How to convert a String to Integer or Float".
If I wanted to convert a numerical string input to its "most appropriate type" using Ruby's built-in conversion mechanics, I could do something like this:
def convert(input)
value = Integer(input) rescue nil
value ||= Float(input) rescue nil
value ||= Rational(input) rescue nil
value
end
convert('1') #=> 1
convert('1_000') #=> 1000
convert('0xff') #=> 255
convert('0.5') #=> 0.5
convert('1e2') #=> 100.0
convert('1/2') #=> (1/2)
convert('foo') #=> nil
But this brute-force method-calling looks dirty. Is there a more elegant way to approach this? Can I check whether a value is a valid input for Integer()
, Float()
or Rational()
so I could call these methods in a more controlled manner?
Using a trailing rescue makes me cringe as it can obscure problems with the underlying code since it traps Exception not ArgumentError, which is what the failed attempted conversions would raise. This isn't as concise but it'd handle the appropriate exception:
def convert(input)
value = begin
Integer(input)
rescue ArgumentError
nil
end
value ||= begin
Float(input)
rescue ArgumentError
nil
end
value ||= begin
Rational(input)
rescue ArgumentError
nil
end
value
end
convert('1') # => 1
convert('1_000') # => 1000
convert('0xff') # => 255
convert('0.5') # => 0.5
convert('1e2') # => 100.0
convert('1/2') # => (1/2)
convert('foo') # => nil
After thinking about it a bit it seems like that can be DRY'd down to:
def convert(input)
[:Integer, :Float, :Rational].each do |m|
begin
return Kernel.method(m).call(input)
rescue ArgumentError
end
end
nil
end
convert('1') # => 1
convert('1_000') # => 1000
convert('0xff') # => 255
convert('0.5') # => 0.5
convert('1e2') # => 100.0
convert('1/2') # => (1/2)
convert('foo') # => nil
As pointed out by Jörn, the above wasn't a good example. I was using Kernel to get at Integer()
, Float()
and Rational
because that's where they're defined, but really Object was the place to look since it inherits from Kernel.
And it'd been one of those days when I knew there was a good way to call the method indirectly, but call
was sticking in my mind, not send
as Stephan pointed out. So, here's a cleaner way of doing it, starting with:
return Object.send(m, input)
But, that could be reduced to:
return send(m, input)
resulting in:
def convert(input)
[:Integer, :Float, :Rational].each do |m|
begin
return send(m, input)
rescue ArgumentError
end
end
nil
end
convert('1') # => 1
convert('1_000') # => 1000
convert('0xff') # => 255
convert('0.5') # => 0.5
convert('1e2') # => 100.0
convert('1/2') # => (1/2)
convert('foo') # => nil
Since for some reason you prefer "0.3"
to be converted to 3e-1
rather than to 3/10
, this might be done in more explicit manner. After all, under the hood, there is the same recognition mechanism in ruby parser:
def convert input
raise unless String === input && input[/\A_|_\z|__/].nil?
input = input.strip.delete('_')
case input
when /\A-?\d+\z/ then Integer(input)
when /\A-?0x[\da-f]+\z/i then Integer(input)
when /\A-?(\d*\.)?\d+(e-?\d+)?\z/i then Float(input)
when /\A-?(\d*\.)?\d+(e-?\d+)?\/\d+\z/i then Rational(input)
end
end
And that works as expected :)
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