How to test if a string is basically an integer in quotes using Ruby
Asked Answered
B

19

156

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 I'm assuming there is a helper for this that I am not aware of.

Bleary answered 5/8, 2009 at 21:28 Comment(3)
possible duplicate of Test if string is a number in Ruby on RailsRavelin
Be careful using solutions relying on regular expressions. Benchmarks show that they run much more slowly than regular code.Friction
There is no canonical way to turn a ruby string integer into an integer?Floodlight
A
146

You can use regular expressions. Here is the function with @janm's suggestions.

class String
    def is_i?
       !!(self =~ /\A[-+]?[0-9]+\z/)
    end
end

An edited version according to comment from @wich:

class String
    def is_i?
       /\A[-+]?\d+\z/ === self
    end
end

In case you only need to check positive numbers

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  
Absalom answered 5/8, 2009 at 21:59 Comment(10)
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?" ...Abercromby
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.Absalom
You've got the right idea, but it doesn't match binary or hex literals (see my edited solution below).Pfeifer
Sarah, that's not a bug, that's a feature!Abercromby
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. ;)Pfeifer
Two comments. You can use /regexp/ === self instead of the !!(self =~ /regexp/) construct. You can use character class '\d' instead of [0-9]Niggerhead
You should use \A \Z instead of ^ $. EXAMPLE: "\n9" should not be valid!Frazer
@cintrzyk: Right, thanks. Used \z instead of \Z to match the end of the string as \Z disregards a trailing new line char.Absalom
This is quite nice answer. Just an observation, @Frazer noticed a place for update 4 years after the answer has been made. Now it is updated, but all other people who used the previous answer might have used not so perfect algorithm :)Vaporimeter
The simplest regex for an integer probably is /^\d+$/Transported
P
204

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

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
Pfeifer answered 5/8, 2009 at 21:35 Comment(15)
Couldn't you replace self.to_i.to_s == self with Integer self rescue false ?Quadrennium
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.Pfeifer
For the record, I think inline "rescue nil" is pretty useful, at least if used with discretion.Buzzell
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.Pfeifer
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.Abercromby
I liked this solution until dealing with commas and currency. '$1,000'.to_i.to_s == '$1,000' fails.Mirza
might want true when there's a space, e.g. " 1"Linnette
I'm all in favor for this solution, except for one mostly harmless snag: '-0'.is_integer? == falseAngelita
Thanks for this solution – I've been using it a lot. Dealing with Rails where my model IDs might also be an actual Integer, I've added a clause to return true if value.is_a? Integer.Favorite
I like your simple solution, a better name for it might be "is_id" - if you are checking for conversion to an id, you shouldn't have commas, whitespace, negative numbers or any of that. If you want to allow for leading zeros, a simpler regex can be tacked on: self.to_i.to_s == self.sub(/^0+/,"")Shamefaced
It's a waste of cycles to iterate on several regexes. Rather, put them all in parens, join them with |, and run it once.Coolth
I think this solution adds complexity for the sake of strict adherence to a rule of thumb. In the case of using Integer for answering the question of whether a string contains a valid number, it's localized, robust, and easy to reason about code. The regex solution is harder to reason about and has greater potential for edge cases that need to be handled, in some cases complicating it further. The accepted answer had one such issue for several years, and this answer still has that same issue at the time of this writing. It matches strings such as "abc\n123\ndef".Phosphene
'1.0'.to_i.to_s != '1.0'Sailesh
'01'.to_i.to_s != '01'Baltimore
'+2'.is_integer? is falseSheppard
A
146

You can use regular expressions. Here is the function with @janm's suggestions.

class String
    def is_i?
       !!(self =~ /\A[-+]?[0-9]+\z/)
    end
end

An edited version according to comment from @wich:

class String
    def is_i?
       /\A[-+]?\d+\z/ === self
    end
end

In case you only need to check positive numbers

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  
Absalom answered 5/8, 2009 at 21:59 Comment(10)
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?" ...Abercromby
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.Absalom
You've got the right idea, but it doesn't match binary or hex literals (see my edited solution below).Pfeifer
Sarah, that's not a bug, that's a feature!Abercromby
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. ;)Pfeifer
Two comments. You can use /regexp/ === self instead of the !!(self =~ /regexp/) construct. You can use character class '\d' instead of [0-9]Niggerhead
You should use \A \Z instead of ^ $. EXAMPLE: "\n9" should not be valid!Frazer
@cintrzyk: Right, thanks. Used \z instead of \Z to match the end of the string as \Z disregards a trailing new line char.Absalom
This is quite nice answer. Just an observation, @Frazer noticed a place for update 4 years after the answer has been made. Now it is updated, but all other people who used the previous answer might have used not so perfect algorithm :)Vaporimeter
The simplest regex for an integer probably is /^\d+$/Transported
S
73

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

def is_num?(str)
  !!Integer(str)
rescue ArgumentError, TypeError
  false
end

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. If that's not the behaviour you want, you can add 10 as a second argument to Integer, so the number is always interpreted as base 10.

Sulfonal answered 5/8, 2009 at 21:31 Comment(19)
Dude...provoking an exception just to convert a number? Exceptions are not control flow.Pfeifer
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.Monroemonroy
For those wondering why, Integer("09") is not valid because the "0" makes it octal, and 9 is not a valid octal number. osdir.com/ml/lang.ruby.general/2002-08/msg00247.htmlShani
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.Pfeifer
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.Monroemonroy
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.Hosanna
Also beware that using Integer(str) will convert strings like 0x26, so a better method to use might be Integer(str, 10). The 10 will only accept base 10 numbers.Superheterodyne
@SarahMei: @Sulfonal isn't using exceptions as control flow. It'd be that way if they did something like raise ArgumentError and later rescued from that exception.Reclaim
-1 since you shouldn't use exception for control flow. It is considered an anti-pattern in computer science!Absalom
@Absalom So is reinventing the wheel.Sulfonal
@sepp2k: +1 for funny, but maybe SO should consider which answers to reinvent the wheel with. I did a quick benchmark of my answer vs yours. Here are the results: Benchmark result: gist.github.com/anonymous/40c2f51e98ae41f9249e Source: gist.github.com/anonymous/12a8e22cffb4208deb21Absalom
Huzzah for a working solution to a ridiculous problem. There should be a better way to do this within ruby. The need for regexp to solve this problem is far more absurd. Complaints about using exceptions as control flow are dogmatic nonsense, this is a working solution to a problem which (while ugly) works! If you have a bullet proof regexp solution, make a PR against the ruby core!Medlar
This is a hell of a lot better than trying to work through somebody's (possibly) buggy Regex. Regarding exceptions: compare this to other languages like C# that can throw FormatException when calling Int32.Parse(). Java does something similar. Look at how Python solves this problem (basically you'll see all kinds of long complex solutions that parse manually, use regex, or throw exceptions).Paring
@Absalom regarding exception control flow "anti-pattern" in computer science--this isn't universally true. This isn't a Python question, but consider how exceptions can be used in Python: (1) error handling, (2) event notification, (3) special-case handling, (4) termination actions, (5) unusual control flows (Mark Lutz, "Learning Python", 5th ed, pg 1082-3). From Lut'z book, also check out the idiom for breaking out of multiple nested loops (using exceptions) on page 1145. And read how Exceptions Aren't Always Errors pg 1146. Ruby can do these things too. Duck typed languages aren't so dogmatic.Paring
Note that if str happens to be a Float value, like 1.23, this will return true because Integer(1.23) will convert the number to integer.Marlomarlon
@Marlomarlon Nope. It won't. pry(main)> Integer "1.25" rescue false => falsePathogen
@Pathogen That's a string value. I was talking about this: !!Integer(1.25) rescue false => true (note the lack of quotes)Marlomarlon
@Marlomarlon yes, this is a String, because this question is about strings...Pathogen
@MatthewKraus Yes, it isn't universally true. They are considered anti-pattern for control-flow because they are usually more expensive. Now, are there times where they should be used? Sure - exceptional cases - hence the name. That however does not mean that they should be used all the time, especially for something like what the question asked. Check the benchmark I provided - it's evident from it that exceptions are really expensive in this case. The only time where this outperforms regex is when an exception is not thrown. This, in itself, tells the whole story.Absalom
N
53

Ruby 2.6.0 enables casting to an integer without raising an exception, and will return nil if the cast fails. And since nil mostly behaves like false in Ruby, you can easily check for an integer like so:

if Integer(my_var, exception: false)
  # do something if my_var can be cast to an integer
end
Nesta answered 3/1, 2019 at 13:5 Comment(2)
This is the cleanest of all!Wilke
If you need some kind of strict type validation, this is the way to go, as "2hey".to_i == 2 returns true, but Integer("2hey", exception: false) returns nilInterface
H
26

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
Hosanna answered 5/8, 2009 at 22:11 Comment(1)
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.Hosanna
V
26
"12".match(/^(\d)+$/)      # true
"1.2".match(/^(\d)+$/)     # false
"dfs2".match(/^(\d)+$/)    # false
"13422".match(/^(\d)+$/)   # true
Valentine answered 8/8, 2013 at 15:24 Comment(4)
It doesn't return true and false but MatchData instances and nilCharmainecharmane
It is not what it returns, but if it matchesValentine
Wrap it with !! or use present? if you need a boolean !!( "12".match /^(\d)+$/ ) or "12".match(/^(\d)+$/).present? (the latter requiring Rails/activesupport)Hag
This regular expression does not take a sign into account: negative numbers are valid integer numbers too. You're now testing for valid natural numbers or zero.Outdare
P
8
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.
Perigee answered 5/8, 2009 at 21:42 Comment(5)
+1 for naming it #integer?, -1 for cluttering up String with it :-PMonroemonroy
Where else would it go? integer?("a string") ftl.Perigee
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.Monroemonroy
Same comment as above: exceptions shouldn't be used for control flow.Pfeifer
Downside: this solution is wasting one conversion.Hosanna
C
8

The Best and Simple way is using Float

val = Float "234" rescue nil

Float "234" rescue nil #=> 234.0

Float "abc" rescue nil #=> nil

Float "234abc" rescue nil #=> nil

Float nil rescue nil #=> nil

Float "" rescue nil #=> nil

Integer is also good but it will return 0 for Integer nil

Certificate answered 29/11, 2013 at 6:13 Comment(3)
I'm glad I noticed your answer. Otherwise I would have gone with "Integer" when I needed "Float".Host
This is simple but the best answer! We don't really need fancy patch to String class in most of the cases. This works best for me!Inchon
(Float(value) rescue false) ? Float(value).to_s==value ? Float(value) : Integer(value) : valueTallith
F
7

I prefer:

config/initializers/string.rb

class String
  def number?
    Integer(self).is_a?(Integer)
  rescue ArgumentError, TypeError
    false
  end
end

and then:

[218] pry(main)> "123123123".number?
=> true
[220] pry(main)> "123 123 123".gsub(/ /, '').number?
=> true
[222] pry(main)> "123 123 123".number?
=> false

or check phone number:

"+34 123 456 789 2".gsub(/ /, '').number?
Foxtrot answered 12/5, 2016 at 17:47 Comment(0)
W
5

Personally I like the exception approach although I would make it a little more terse:

class String
  def integer?(str)
    !!Integer(str) rescue false
  end
end

However, as others have already stated, this doesn't work with Octal strings.

Waiver answered 15/6, 2011 at 14:55 Comment(0)
M
5

A much simpler way could be

/(\D+)/.match('1221').nil? #=> true
/(\D+)/.match('1a221').nil? #=> false
/(\D+)/.match('01221').nil? #=> true
Myopic answered 12/7, 2015 at 0:33 Comment(0)
E
3
  def isint(str)
    return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/)
  end
Eliseoelish answered 21/12, 2010 at 7:30 Comment(1)
Code only answers are not very useful. Instead provide an explanation of how it works and why it's the appropriate answer. We want to educate for the future so the solution is understood, not solve the immediate question.Friction
C
3

Ruby 2.4 has Regexp#match?: (with a ?)

def integer?(str)
  /\A[+-]?\d+\z/.match? str
end

For older Ruby versions, there's Regexp#===. And although direct use of the case equality operator should generally be avoided, it looks very clean here:

def integer?(str)
  /\A[+-]?\d+\z/ === str
end

integer? "123"    # true
integer? "-123"   # true
integer? "+123"   # true

integer? "a123"   # false
integer? "123b"   # false
integer? "1\n2"   # false
Charmainecharmane answered 17/9, 2013 at 13:29 Comment(0)
S
3

This might not be suitable for all cases simplely using:

"12".to_i   => 12
"blah".to_i => 0

might also do for some.

If it's a number and not 0 it will return a number. If it returns 0 it's either a string or 0.

Selfexecuting answered 1/6, 2015 at 15:4 Comment(1)
Works but it's not recommended, since "12blah".to_i => 12. This might cause some trouble in weird scenarios.Northerly
C
3

Here's my solution:

# /initializers/string.rb
class String
  IntegerRegex = /^(\d)+$/

  def integer?
    !!self.match(IntegerRegex)
  end
end

# any_model_or_controller.rb
'12345'.integer? # true
'asd34'.integer? # false

And here's how it works:

  • /^(\d)+$/is regex expression for finding digits in any string. You can test your regex expressions and results at http://rubular.com/.
  • We save it in a constant IntegerRegex to avoid unnecessary memory allocation everytime we use it in the method.
  • integer? is an interrogative method which should return true or false.
  • match is a method on string which matches the occurrences as per the given regex expression in argument and return the matched values or nil.
  • !! converts the result of match method into equivalent boolean.
  • And declaring the method in existing String class is monkey patching, which doesn't change anything in existing String functionalities, but just adds another method named integer? on any String object.
Chacma answered 24/5, 2017 at 7:37 Comment(2)
Could you add a little explanation to this please?Septuagesima
@Septuagesima - I've done the same. Please let me know if you have any query still.Chacma
P
1

Expanding on @rado's answer above one could also use a ternary statement to force the return of true or false booleans without the use of double bangs. Granted, the double logical negation version is more terse, but probably harder to read for newcomers (like me).

class String
  def is_i?
     self =~ /\A[-+]?[0-9]+\z/ ? true : false
  end
end
Peruzzi answered 25/9, 2014 at 23:13 Comment(1)
Consider that using regular expressions forces Ruby to do a lot more work so if this is used in a loop it'll slow the code. Anchoring the expression helps, but regex are still significantly slower.Friction
D
1

For more generalised cases (including numbers with decimal point), you can try the following method:

def number?(obj)
  obj = obj.to_s unless obj.is_a? String
  /\A[+-]?\d+(\.[\d]+)?\z/.match(obj)
end

You can test this method in an irb session:

(irb)
>> number?(7)
=> #<MatchData "7" 1:nil>
>> !!number?(7)
=> true
>> number?(-Math::PI)
=> #<MatchData "-3.141592653589793" 1:".141592653589793">
>> !!number?(-Math::PI)
=> true
>> number?('hello world')
=> nil
>> !!number?('hello world')
=> false

For a detailed explanation of the regex involved here, check out this blog article :)

Detail answered 15/6, 2016 at 16:43 Comment(1)
Isn't necessary to call obj.is_a? String because String#to_s will return itself, which I guess doesn't require too much processing compared with the .is_a? call. This way, you'll be making only one call in this line instead of one or two. Also, you could include directly !! inside the number? method, because by convention, a method name that ends with ? is supposed to return a boolean value. Regards!Michelinamicheline
C
-3

One liner in string.rb

def is_integer?; true if Integer(self) rescue false end
Constellation answered 23/3, 2016 at 18:24 Comment(0)
G
-3

I'm not sure if this was around when this question is asked but for anyone that stumbles across this post, the simplest way is:

var = "12"
var.is_a?(Integer) # returns false
var.is_a?(String) # returns true

var = 12
var.is_a?(Integer) # returns true
var.is_a?(String) # returns false

.is_a? will work with any object.

Geronto answered 21/4, 2016 at 10:58 Comment(1)
That's not what the original question is asking. OP wants to know if the string is an integer also. e.g. "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == trueRodrigorodrigue

© 2022 - 2024 — McMap. All rights reserved.