For web programming, numbers come in as strings. but to_i will convert "5abc" to 5 and "abc" to 0, both wrong answers. To catch these, I wrote:
def number_or_nil( s )
number = s.to_i
number = nil if (number.to_s != s)
return number
end
Is there a neater, more Ruby-natural way of accomplishing this conversion and detecting that the string wasn't intended as a number?
Use Integer(string)
It will raise an ArgumentError error if the string cannot convert to an integer.
Integer('5abc') #=> ArgumentError: invalid value for Integer(): "5abc"
Integer('5') #=> 5
You'd still need your number_or_nil method if you want the behavior to be that nil is returned when a string cannot be converted.
def number_or_nil(string)
Integer(string || '')
rescue ArgumentError
nil
end
You should be careful to rescue from a particular exception. A bare rescue (such as "rescue nil") will rescue from any error which inherits from StandardError and may interfere with the execution of your program in ways you don't expect. Integer() will raise an ArgumentError, so specify that.
If you'd rather not deal with exceptions and just prefer a shorter version of your number_or_nil you can take advantage of implicit return values and write it as:
def number_or_nil(string)
num = string.to_i
num if num.to_s == string
end
number_or_nil '5' #=> 5
number_or_nil '5abc' #=> nil
This will work the way you expect.
Since at least Ruby 2.6, the kernel functions Integer, Float, etc. accept an exception keyword argument that does the job:
> Integer('42', exception: false)
=> 42
> Integer('x42', exception: false)
=> nil
> Integer('x42')
ArgumentError (invalid value for Integer(): "x42")
> Integer('', exception: false)
=> nil
> Integer('')
ArgumentError (invalid value for Integer(): "")
> Integer(nil, exception: false)
=> nil
> Integer(' 42 ', exception: false)
=> 42
> Integer(' 4 2 ', exception: false)
=> nil
Note that Integer also leaves you the control of the base, something that to_i does not support:
> '0xf'.to_i
=> 0
> Integer('0xf')
=> 15
> Integer('10', 8)
=> 8
When the base is specified, the radix-prefix (0x... etc.) must be consistent (if present):
> Integer('0xf', 10)
=> ArgumentError(invalid value for Integer(): "0xf")
> Integer('0xf', 16)
=> 15
> Integer('f', 16)
=> 15
Use a simple regex to check str is an integer.
def number_or_nil(str)
str.to_i if str[/^-?\d+$/] and str.line.size == 1
end
Related
Is it a good idea to return an object whose type changes depending on the method’s inner logic?
E.g.:
class Error
attr_reader :details
def initialize(details)
#details = details
end
end
def div(a, b)
return Error.new("error: division by zero") if b == 0
a / b
end
# declare foo, bar here
result = div(foo, bar)
if result.is_a?(Error)
puts result.details
else
puts "result of division: #{result}"
end
As you can tell, the div method returns either an Error instance, or an Integer instance.
Is it a bad practice (and why)? Does it violate the single responsibility principle?
By the way, I do get that another option would be to return a hash { error: error, div_result: div_result }, but I’m curious if it could be replaced with only one single object.
Thank you.
You seem to be reinventing the wheel. Ruby already has Exception Handling and it also includes a ZeroDivisionError which is raised when dividing an integer by zero:
def div(a, b)
a / b
end
begin
result = div(6, 0)
puts "result of division: #{result}"
rescue ZeroDivisionError => e
puts "error: #{e.message}"
end
# prints "error: divided by 0"
Note that your div method isn't needed either, you can just as well write result = 6 / 0.
Is it a good idea to return an object whose type changes depending on the method’s inner logic?
"type" is a difficult term in Ruby. I assume you mean something like "an instance of a specific class" like an integer or a string? It really depends. For most methods it would certainly be a good idea, but there are good exceptions to this rule.
Integer#+ for example returns a result based on its argument: (you could argue that these are all subclasses of Numeric)
1 + 2 #=> 3 (Integer)
1 + 2.0 #=> 3.0 (Float)
1 + 2r #=> (3/1) (Rational)
String#index usually returns an integer but it may also return nil to indicate "not found":
"hello".index("o") #=> 4 (Integer)
"hello".index("x") #=> nil (NilClass)
Array#[] returns whatever object is stored at the specified index, thus having arbitrary return values:
a = [123, :foo, "bar"]
a[0] #=> 123 (Integer)
a[1] #=> :foo (Symbol)
a[2] #=> "bar" (String)
a[3] #=> nil (NilClass)
I'm doing a simple calculator using Ruby as practice. Everything is fine except how do I identify if a character/symbol I input is a number or not using if-else statement.
For example:
Enter first number: a
Error: Enter correct number
Enter first number: -
Error: Enter correct number
Enter first number: 1
Enter second number:b
Error: Enter correct number
Enter second number: 2
Choose operator (+-*/): *
The product is: 2
This is the code I input first:
print "Enter first number: "
x = gets.to_i
print "Enter second number: "
y = gets.to_i
print "Choose operator (+-*/): "
op = gets.chomp.to_s
I will use if-else statement to identify if the number input is a number or not
If you wish to test if the string represents an integer or float use Kernel#Integer or Kernel#Float with the optional second argument (a hash) having the value of the key :exception equal to false.
For example,
Integer('-123', exception: false)
#=> -123
Integer("0xAa", exception: false)
#=> 170
Integer('12.3', exception: false)
#=> nil,
Integer('12a3', exception: false)
#=> nil
Float('123', exception: false)
#=> 123.0
Float("1.2e3", exception: false)
#=> 1200.0
Float('12a3', exception: false)
#=> nil,
Float('1.2.3', exception: false)
#=> nil
Note
Integer("123\n", exception: false)
#=> 123
shows you don't have to chomp before testing if the string represents an integer (similar with Float).
Some of the examples illustrate a limitation or complication when using a regular expression to test whether a string represents an integer or float.
Create a new String method
irb(main):001:0> class String
irb(main):002:1> def number?
irb(main):003:2> Float(self) != nil rescue false
irb(main):004:2> end
irb(main):005:1> end
irb(main):012:0> x = gets
1
=> 1
irb(main):013:0> x.number?
=> true
irb(main):009:0> x = gets
s
=> "s\n"
irb(main):010:0> x.number?
=> false
This typical way to do this would be to match it with a regexp:
x = gets.chomp
if x.match?(/^\d+$/)
puts "#{x} is a number"
else
puts "#{x} is not a number"
end
This tests if the given string matches the pattern ^\d+$, which means "start of line, one or more digit characters, then end of line". Note that this won't match characters like "," or "." - only a string of the digits 0-9. String#match? will return a boolean indicating if the string matches the given pattern or not.
use this ruby method
.is_a?(Integer)
For example:
input = gets.chomp
input.is_a?(Integer) # => true || false
I have a function like this:
def check_if_correct_type(type, value)
# nil.test!
# eval(type.classify(value)) rescue return false
# true
case type
when "integer"
!!Integer(value) rescue return false
when "float"
!!Float(value) rescue return false
else
return true
end
true
end
A sample would be
check_if_correct_type("integer", "a")
I tried changing the function like this:
check_if_correct_type(type, value)
!!(eval(type.classify(value))) rescue return false
true
end
This is throwing errors. How do I fix this. I am fairly new to meta programming so kind of lost.
Update 1:
"adfadf".kind_of?(String) #=> true
123.kind_of?(String) #=> false
# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum) #=> true
12.3.kind_of?(Float) #=> true
"sadf".kind_of?(Float) #=> false
12.kind_of?(Float) #=> false
The above will not work for me as the kind_of? function will find the type of the object where as for me the answer requires to be like this:
check_if_correct_type("integer", "1221") #=> true
check_if_correct_type("float", "1.24") #=> true
check_if_correct_type("string", "asds12") #=> true
check_if_correct_type("float", "asdasd1.24") #=> false
where as
"1.24".kind_of?(Float) #=> false
That is why conversion works for me. Hope the question is more clear now.
Update 2:
This is what I get if I use public send.
!!public_send("integer".capitalize("1"))
ArgumentError: wrong number of arguments (1 for 0)
from (pry):4:in capitalize'
[5] pry(main)> !!public_send("integer".classify("1"))
ArgumentError: wrong number of arguments (1 for 0)
from /home/aravind/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/string/inflections.rb:187:inclassify'
Note: classify is a part of Ruby on Rails and not Ruby.
I suggest you write your method as follows:
def correct_type?(type, str)
case type.downcase
when "integer"
!!to_integer(str)
when "float"
!!to_float(str)
else
raise ArgumentError, "type must be 'integer' or 'float'"
end
end
where to_integer(value) (to_float(value)) is a method that returns value.to_i (value.to_f) if value is the string representation of an integer (a float), else returns nil. The methods to_integer and to_float are useful because they tell you both whether the string can be converted to the given numerical type, and if it can, give you the numerical value.
Before considering how you might implement to_integer and to_float, I would like to call into question the need for correct_type?. Rather than:
str = "33"
if correct_type?("integer", str)
n = str.to_i
puts n
else
...
end
would it not be better to write:
if (n = to_integer("33"))
puts n
else
...
end
There are basically two ways to write the methods to_integer and to_float. The first is the approach you took:
def to_integer(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
Integer(s) rescue nil
end
def to_float(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
return nil if to_integer(s)
Float(s) rescue nil
end
to_integer("3") #=> 3
to_integer("-3") #=> -3
to_integer("+ 3") #=> 3
to_integer("cat") #=> nil
to_integer("3.14") #=> nil
to_integer(:cat) #=> ArgumentError: ArgumentError
to_float("3.14") #=> 3.14
to_float("-3.14") #=> -3.14
to_float("+ 3.14") #=> 3.14
to_float("cat") #=> nil
to_float("3") #=> nil
to_float(:cat) #=> ArgumentError: ArgumentError
The second approach is to use a regular expression:
def to_integer(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
s[/^[+-]?\s*\d+$/] ? s.to_i : nil
end
def to_float(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
return nil if to_integer(s)
s[/^[+-]?\s*\d+\.\d+$/] ? s.to_f : nil
end
to_integer("3") #=> 3
to_integer("-3") #=> -3
to_integer("+ 3") #=> 3
to_integer("cat") #=> nil
to_integer("3.14") #=> nil
to_integer(:cat) #=> ArgumentError: ArgumentError
to_float("3.14") #=> 3.14
to_float("-3.14") #=> -3.14
to_float("+ 3.14") #=> 3.14
to_float("cat") #=> nil
to_float("3") #=> nil
to_float(:cat) #=> ArgumentError: ArgumentError
There is no need to use eval to send a message. You can just use send instead:
def check_if_correct_type(type, value)
!!send(type.capitalize, value) rescue return false
true
end
Note: there is no method named classify anywhere in either the Ruby core library or the Ruby standard libraries. Note also that it is a very bad idea, to just blindly catch all exceptions.
I don't see a point of using metaprogramming for this example. You should avoid using it where there's no need for it. Generally speaking, your program's logic should be:
a) Check the type of the value entered.
b) Compare the type with the type entered as argument. Or in code:
def check_if_correct_type(type, value)
actual_type = value.class.name
return actual_type.downcase == type.downcase
end
p check_if_correct_type('string', 'test') #=> true
p check_if_correct_type('integer', 'test2') #=> false
This could could be made even shorter in one line, but did it in two to demonstrate more clearly what's going on.
If you want to check an object's class, the right way is this:
"adfadf".kind_of?(String) #=> true
123.kind_of?(String) #=> false
# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum) #=> true
12.3.kind_of?(Float) #=> true
"sadf".kind_of?(Float) #=> false
12.kind_of?(Float) #=> false
There is no reason to be using the Integer() or Float() methods to check for a type. Those are type conversion methods, they will convert other types to Float or Fixnum. If you do want to try to convert a type is convertable to Float or numeric, that is one way to do it, but there might be better ways.
In general, you should never plan on raising and rescuing an exception as part of ordinary program flow; one reason is because it is very slow. Exceptions should be used for errors and unusual/exceptional conditions, not routine conditions such that exceptions will be frequently raised.
And definitely don't start bringing eval into it, geez why would you do that?
This is how I have ended up solving my problem
def check_if_correct_type(type, value)
!!eval("#{type.classify}(value)") rescue return false
true
end
Sample output for this function is below incase you are wondering if it words or not
[25] pry(main)> value = "1"
=> "1"
[26] pry(main)> !!eval("#{type.classify}(value)")
=> true
[27] pry(main)> value = "a"
=> "a"
[28] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Float(): "a"
from (pry):28:in `eval'
[29] pry(main)> value = "1.4"
=> "1.4"
[30] pry(main)> type = "integer"
=> "integer"
[31] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Integer(): "1.4"
from (pry):31:in `eval'
What's the most-efficient manner to remove beginning and ending spaces around a string, then convert the string to nil if the resulting value is zero-length?
For example:
> a=''
> squash(a)
=> nil
> a=' '
> squash(a)
=> nil
> a=' xyz '
> squash(a)
=> 'xyz'
> a=nil
> squash(a)
=> nil
Thus far:
def squash(value)
return nil if value.nil?
value.strip!
(value.blank? ? nil : value)
end
Seems like there could be a more-terse way of implementing this.
** edit **
While I am working in Rails, it would be nice if the answer would contain a Ruby-only implementation, too.
I should emphasize that the implementation needs to be able to handle a string with a nil value.
Assuming you want this for rails (otherwise blank? is undefined) you can use presence method:
def squash(value)
value && value.strip.presence
end
In pure ruby, I would do:
def squash(value)
return unless value
value = value.strip
value unless value.empty?
end
This will work with plain Ruby:
def squash(str)
str = str.to_s.strip
str unless str.empty?
end
Here's one way:
def squash(str)
(str && str[/\S/]) ? str.strip : nil
end
/\S/ looks for a character that is not whitespace.
squash " My dog has fleas. " #=> "My dog has fleas."
squash " " #=> nil
squash nil #=> nil
Reader challenge
I tried to also implement squash!, that would convert the argument str in place. If str is nil, just leave it alone. If str contains a least one non-whitespace character, then str.strip!. However, I could not figure out a way to convert a string to nil. I wanted to do this when the string is empty or contains only whitespace, but the problem is to convert any string, or more generally, any non-nil object, to nil, when the object is received as a method argument. Can it be done? [Edit: #Stefan says the type cannot be changed. I'm sure he's right, but I would like to see where that is written and understand why it is not permitted. Anyone? tidE].
This handles all your examples.
def squash(value)
value.to_s.strip.empty? ? nil : value.strip
end
Just adding this because it's short:
def squash(str)
str.to_s[/\S(.*\S)?/]
end
squash(nil) #=> nil
squash("") #=> nil
squash(" ") #=> nil
squash("a") #=> "a"
squash(" a") #=> "a"
squash("a ") #=> "a"
squash(" a ") #=> "a"
squash(" foo ") #=> "foo"
squash(" foo bar ") #=> "foo bar"
Here's a plain ruby version:
def squash(str)
str && str.strip! && (str unless str.empty?)
end
Update - If you want a version without side effects:
def squash(str)
str && (x = str.strip) && (x unless x.empty?)
end
If you want the method name squash with argument value.
def squash(value)
return value unless value.instance_of?(String)
return if value.strip!&.empty?
value
end
Features:
Works either on pure Ruby or Ruby on Rails
Works with other data types than string as well, for example, you can pass a number if you want
Testing:
squash('ok')
#=> ok
squash('')
#=> nil
squash(' ')
#=> nil
squash(' xyz ')
#=> 'xyz'
squash('xyz ')
#=> 'xyz'
squash(' xyz')
#=> 'xyz'
squash(123)
#=> 123
squash(nil)
#=> nil
Note:
I use safe navigation operator, which was released in Ruby 2.3.0. So make sure before using it.
irb(main):001:0> s = " string "
=> " string "
irb(main):002:0> s.strip!
=> "string"
irb(main):003:0> s.blank?
NoMethodError: undefined method `blank?' for "string":String
from (irb):3
from C:/RUBY/BIN/irb:12:in `'
irb(main):004:0>
I think blank is not Ruby but Rails? Anyway, what's wrong with
(value.length == 0 ? nil : value)
or even better
value.empty? ? nil : value
At least everybody would understand what the intention is here.
Why does ruby addition cannot coerce given string to fixnum and vice verca?
irb>fixnum = 1
=> 1
irb> fixnum.class
=> Fixnum
irb> string = "3"
=> "3"
irb> string.class
=> String
irb> string.to_i
=> 3
irb> fixnum + string
TypeError: String can't be coerced into Fixnum
from (irb):96:in `+'
from (irb):96
from :0
irb(main):097:0>
Because ruby doesn't know whether you want to convert the string to int or the int to string. In e.g. java 1 + "3" is "13". Ruby prefers to raise an error, so that the user can decide whether to to_s the one operand or to_i the other, rather than doing the wrong thing automatically.
It spits out an error because ruby doesn't know what you want. String + fixnum is an ambiguous statement. It could either mean that you want to add the int value of the string and the number, or the string value of the int and the string. For example:
str = "5"
num = 3
puts str.to_i + num
=> 8
puts str + num.to_s
=> 53
Rather than taking a guess as to which of those you want, ruby just throws an exception.
You need to set the variable back to itself, since doing a .to_i returns a integer value, but doesn't modify the original object.
>> string = "3"
=> "3"
>> string.class
=> String
>> string.to_i
=> 3
>> string
=> "3"
>> string.class
=> String
>> string = string.to_i
=> 3
>> string.class
=> Fixnum
Fixnum#+ is just a method. Simplified it works like this:
class Fixnum
def +(other)
if Fixnum === other
# normal operation
else
n1, n2= other.coerce(self)
return n1+n2
end
end
end
coerce is used for automatic numerical type conversion. This is used for example if you do 42 + 3.141. A string in ruby cannot automaticly be converted into numerical values. You could enhance the String class to be able to do this. You just need to do
class String
def coerce(other)
coerced= case other
when Integer
self.to_i
when
self.to_f
end
return coerced, other
end
end
Now you can do
23 + "42" # => 65
0.859 - "4" # => 3.141
This does not work the other way arround. coerce is only for numerical values "23" + 42 won't work. String#+ will not use coerce.
The simplified + is done in Fixnum and not in Integer on purpose. Fixnum and Bignum have their separate methods, because they work internally very different.