tags:

views:

914

answers:

5

I need a function, is_an_integer, where

"12".is_an_integer? returns true
"blah".is_an_integer? returns false

how can i do this in ruby? i would write a regex but im assuming there is a helper for this that i am not aware of

+2  A: 

You can use Integer(str) and see if it raises:

def is_num?(str)
  Integer(str)
rescue ArgumentError
  false
else
  true
end

Addendum: It should be pointed out that while this does return true for "01", it does not for "09", simply because 09 would not be a valid integer literal.

sepp2k
-1??? This is a good answer!
Avdi
Dude...provoking an exception just to convert a number? Exceptions are not control flow.
Sarah Mei
They aren't, but unfortunately this is the canonical way to determine "integerness" of a string in Ruby. Methods using `#to_i` are just too broken because of it's permissiveness.
Avdi
For those wondering why, Integer("09") is not valid because the "0" makes it octal, and 9 is not a valid octal number. http://osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Andrew Grimm
Avdi - if to_i won't work, why not do it with regexes? Provoking an exception may be canonical (though I couldn't find a reference) but it's still a smell. I edited my answer to include a regex solution.
Sarah Mei
Sarah: you can use a Regex but in order to handle all the cases that Ruby does when parsing integers (negative numbers, hex, octal, underscores e.g. 1_000_000) it would be a very big Regex and easy to get wrong. `Integer()` is canonical because with `Integer ()`you know for sure that anything that Ruby considers an integer literal will be accepted, and everything else will be rejected. Duplicating what the language already gives you is arguably a worse code smell than using exceptions for control.
Avdi
If you remove the "else" clause above you will actually get the numeric value back. I would concede that the method name should probably changed then. I still wonder what the test method is needed for. We do not have context information unfortunately.
Robert Klemme
+1  A: 

Well, here's the easy way:

class String
  def is_integer?
    self.to_i.to_s == self
  end
end

>> "12".is_integer?
=> true
>> "blah".is_integer?
=> false

EDIT: I don't agree with the solutions that provoke an exception to convert the string - exceptions are not control flow, and you might as well do it the right way. That said, my solution above doesn't deal with non-base-10 integers. So here's the way to do with without resorting to exceptions:

  class String
    def integer? 
      [                          # In descending order of likeliness:
        /^[-+]?[1-9]([0-9]*)?$/, # decimal
        /^0[0-7]+$/,             # octal
        /^0x[0-9A-Fa-f]+$/,      # hexadecimal
        /^0b[01]+$/              # binary
      ].each do |match_pattern|
        return true if self =~ match_pattern
      end
      return false
    end
  end
Sarah Mei
"01".to_i.to_s != "01"
sepp2k
Couldn't you replace `self.to_i.to_s == self` with `Integer self rescue false` ?
Meredith L. Patterson
You could, but that would be bad form. You don't use exceptions as control flow, and no one's code should ever contain "rescue false" (or "rescue true"). Some simple gsub'ing would make my solution work for edge cases not specified by the OP.
Sarah Mei
For the record, I think inline "rescue nil" is pretty useful, at least if used with discretion.
glenn mcdonald
I know a lot of people use it, and it's certainly aesthetically pleasing. To me though it's an indication that the code needs restructuring. If you're expecting an exception...it's not an exception.
Sarah Mei
I agree that exceptions should not be used as control flow. I don't think that the requirement is that developer oriented numbers be recognized. In non-programmer situations that could be seen as a bug, especially given that possible confusion around leading zeros and octal. Also not consistent with to_i.Your code doesn't handle the "-0123" case. Once you do handle that case, you don't need a separate regexp for octal. You can simply further by using "any?". The only statement in your function could be "[ /re1/, /re2/, /re3/ ].any? { |re| self =~ re }", with no if clauses or returns.
janm
+3  A: 
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. It isn't prefixed with is_. I find that silly on questionmark methods, I like "04".integer? a lot better than "foo".is_integer?.
  2. It uses the sensible solution by sepp2k, which passes for "01" and such.
  3. Object oriented, yay.
August Lilleaas
+1 for naming it #integer?, -1 for cluttering up String with it :-P
Avdi
Where else would it go? `integer?("a string")` ftl.
August Lilleaas
`String#integer?` is the kind of common patch that every Ruby coder and their cousin likes to add to the language, leading to codebases with three different subtly incompatible implementations and unexpected breakage. I learned this the hard way on large Ruby projects.
Avdi
Same comment as above: exceptions shouldn't be used for control flow.
Sarah Mei
+4  A: 

You can use regular expressions:

def isint(str)
   return str =~ /^[-+]?[0-9]+$/
end

heh ... first time coding in ruby ... huray!

[edit] here is the function with janm's suggestions .. thanks man!

class String
    def is_i?
       !!(self =~ /^[-+]?[0-9]+$/)
    end
end

[/edit]

Rado
Not bad. In Ruby you usually omit the "return" keyword if the return value is generated in the last expression in the function. This will also return an integer value of zero, you probably want a boolean, so something like !!(str =~ /^[-+]?[0-9]+$/) would do that. Then you could add it to String and leave out the argument, using "self" instead of "str", and then you could change the name to "is_i?" ...
janm
Thanks! I have absolutely no clue about ruby conventions and practices. I just did a quick google on ruby and regular expressions to see the syntax, changed the regex to apply to the problem at hand, and tested it. It's pretty neat actually .. I may have to give it a longer look when I have more spare time.
Rado
You've got the right idea, but it doesn't match binary or hex literals (see my edited solution below).
Sarah Mei
Sarah, that's not a bug, that's a feature!
janm
Well, given that I got two downvotes because my original solution didn't work on non-base-10 literals, I thought I'd spare him the same experience. ;)
Sarah Mei
+1  A: 

You can do a one liner:

str = ...
int = Integer(str) rescue nil

if int
  int.times {|i| p i}
end

or even

int = Integer(str) rescue false

Depending on what you are trying to do you can also directly use a begin end block with rescue clause:

begin
  str = ...
  i = Integer(str)

  i.times do |j|
    puts j
  end
rescue ArgumentError
  puts "Not an int, doing something else"
end
Robert Klemme
With regard to the topic "exception as control flow": since we do not know how the method at hand is to be used we cannot really judge whether exceptions would fit or not. If the string is input and it is required to be an integer, then providing a non integer would warrant an exception. Although then maybe the handling is not in the same method and we would probably just do Integer(str).times {|i| puts i} or whatever.
Robert Klemme