测试string是否是Ruby on Rails中的数字

时间:2011-04-14 09:56:12

标签: ruby-on-rails ruby string integer

我的应用程序控制器中有以下内容:

def is_number?(object)
  true if Float(object) rescue false
end

以及我的控制器中的以下情况:

if mystring.is_number?

end

该情况导致undefined method错误。我猜我在错误的地方定义了is_number ......?

13 个答案:

答案 0 :(得分:177)

创建is_number?方法。

创建辅助方法:

def is_number? string
  true if Float(string) rescue false
end

然后像这样称呼它:

my_string = '12.34'

is_number?( my_string )
# => true

扩展String类。

如果您希望能够直接在字符串上调用is_number?而不是将其作为辅助函数传递给辅助函数,那么您需要将is_number?定义为{{1的扩展名类,如:

String

然后你可以用:

来调用它
class String
  def is_number?
    true if Float(self) rescue false
  end
end

答案 1 :(得分:29)

class String
  def numeric?
    return true if self =~ /\A\d+\Z/
    true if Float(self) rescue false
  end
end  

p "1".numeric?  # => true
p "1.2".numeric? # => true
p "5.4e-29".numeric? # => true
p "12e20".numeric? # true
p "1a".numeric? # => false
p "1.2.3.4".numeric? # => false

答案 2 :(得分:26)

以下是解决此问题的常用方法的基准。请注意您应该使用哪一个可能取决于预期的错误案例的比例。

  1. 如果它们相对不常见,那么铸造肯定是最快的。
  2. 如果错误的情况很常见且您只是检查整数,那么比较转换状态是一个不错的选择。
  3. 如果错误的情况很常见并且你正在检查花车,那么regexp可能是要走的路
  4. 如果表现无关紧要,请使用您喜欢的内容。 : - )

    整数检查详细信息:

    # 1.9.3-p448
    #
    # Calculating -------------------------------------
    #                 cast     57485 i/100ms
    #            cast fail      5549 i/100ms
    #                 to_s     47509 i/100ms
    #            to_s fail     50573 i/100ms
    #               regexp     45187 i/100ms
    #          regexp fail     42566 i/100ms
    # -------------------------------------------------
    #                 cast  2353703.4 (±4.9%) i/s -   11726940 in   4.998270s
    #            cast fail    65590.2 (±4.6%) i/s -     327391 in   5.003511s
    #                 to_s  1420892.0 (±6.8%) i/s -    7078841 in   5.011462s
    #            to_s fail  1717948.8 (±6.0%) i/s -    8546837 in   4.998672s
    #               regexp  1525729.9 (±7.0%) i/s -    7591416 in   5.007105s
    #          regexp fail  1154461.1 (±5.5%) i/s -    5788976 in   5.035311s
    
    require 'benchmark/ips'
    
    int = '220000'
    bad_int = '22.to.2'
    
    Benchmark.ips do |x|
      x.report('cast') do
        Integer(int) rescue false
      end
    
      x.report('cast fail') do
        Integer(bad_int) rescue false
      end
    
      x.report('to_s') do
        int.to_i.to_s == int
      end
    
      x.report('to_s fail') do
        bad_int.to_i.to_s == bad_int
      end
    
      x.report('regexp') do
        int =~ /^\d+$/
      end
    
      x.report('regexp fail') do
        bad_int =~ /^\d+$/
      end
    end
    

    浮动检查细节:

    # 1.9.3-p448
    #
    # Calculating -------------------------------------
    #                 cast     47430 i/100ms
    #            cast fail      5023 i/100ms
    #                 to_s     27435 i/100ms
    #            to_s fail     29609 i/100ms
    #               regexp     37620 i/100ms
    #          regexp fail     32557 i/100ms
    # -------------------------------------------------
    #                 cast  2283762.5 (±6.8%) i/s -   11383200 in   5.012934s
    #            cast fail    63108.8 (±6.7%) i/s -     316449 in   5.038518s
    #                 to_s   593069.3 (±8.8%) i/s -    2962980 in   5.042459s
    #            to_s fail   857217.1 (±10.0%) i/s -    4263696 in   5.033024s
    #               regexp  1383194.8 (±6.7%) i/s -    6884460 in   5.008275s
    #          regexp fail   723390.2 (±5.8%) i/s -    3613827 in   5.016494s
    
    require 'benchmark/ips'
    
    float = '12.2312'
    bad_float = '22.to.2'
    
    Benchmark.ips do |x|
      x.report('cast') do
        Float(float) rescue false
      end
    
      x.report('cast fail') do
        Float(bad_float) rescue false
      end
    
      x.report('to_s') do
        float.to_f.to_s == float
      end
    
      x.report('to_s fail') do
        bad_float.to_f.to_s == bad_float
      end
    
      x.report('regexp') do
        float =~ /^[-+]?[0-9]*\.?[0-9]+$/
      end
    
      x.report('regexp fail') do
        bad_float =~ /^[-+]?[0-9]*\.?[0-9]+$/
      end
    end
    

答案 3 :(得分:14)

依赖引发的异常并不是最快,最可读也不可靠的解决方案 我会做以下事情:

my_string.should =~ /^[0-9]+$/

答案 4 :(得分:6)

不,你只是错误地使用它。你的is_number?有一个论点。你没有参数就叫它

你应该做is_number吗?(mystring)

答案 5 :(得分:5)

Tl; dr:使用正则表达式方法。它比接受答案中的救援方法快39倍,并且还处理“1,000”

等案例
def regex_is_number? string
  no_commas =  string.gsub(',', '')
  matches = no_commas.match(/-?\d+(?:\.\d+)?/)
  if !matches.nil? && matches.size == 1 && matches[0] == no_commas
    true
  else
    false
  end
end

-

@Jakob S接受的答案在很大程度上起作用,但捕获异常可能非常慢。此外,救援方法在类似“1,000”的字符串上失败。

让我们定义方法:

def rescue_is_number? string
  true if Float(string) rescue false
end

def regex_is_number? string
  no_commas =  string.gsub(',', '')
  matches = no_commas.match(/-?\d+(?:\.\d+)?/)
  if !matches.nil? && matches.size == 1 && matches[0] == no_commas
    true
  else
    false
  end
end

现在有些测试用例:

test_cases = {
  true => ["5.5", "23", "-123", "1,234,123"],
  false => ["hello", "99designs", "(123)456-7890"]
}

运行测试用例的一些代码:

test_cases.each do |expected_answer, cases|
  cases.each do |test_case|
    if rescue_is_number?(test_case) != expected_answer
      puts "**rescue_is_number? got #{test_case} wrong**"
    else
      puts "rescue_is_number? got #{test_case} right"
    end

    if regex_is_number?(test_case) != expected_answer
      puts "**regex_is_number? got #{test_case} wrong**"
    else
      puts "regex_is_number? got #{test_case} right"
    end  
  end
end

以下是测试用例的输出:

rescue_is_number? got 5.5 right
regex_is_number? got 5.5 right
rescue_is_number? got 23 right
regex_is_number? got 23 right
rescue_is_number? got -123 right
regex_is_number? got -123 right
**rescue_is_number? got 1,234,123 wrong**
regex_is_number? got 1,234,123 right
rescue_is_number? got hello right
regex_is_number? got hello right
rescue_is_number? got 99designs right
regex_is_number? got 99designs right
rescue_is_number? got (123)456-7890 right
regex_is_number? got (123)456-7890 right

是时候做一些性能基准了:

Benchmark.ips do |x|

  x.report("rescue") { test_cases.values.flatten.each { |c| rescue_is_number? c } }
  x.report("regex") { test_cases.values.flatten.each { |c| regex_is_number? c } }

  x.compare!
end

结果:

Calculating -------------------------------------
              rescue   128.000  i/100ms
               regex     4.649k i/100ms
-------------------------------------------------
              rescue      1.348k (±16.8%) i/s -      6.656k
               regex     52.113k (± 7.8%) i/s -    260.344k

Comparison:
               regex:    52113.3 i/s
              rescue:     1347.5 i/s - 38.67x slower

答案 6 :(得分:4)

这就是我这样做的方式,但我认为必须有更好的方法

object.to_i.to_s == object || object.to_f.to_s == object

答案 7 :(得分:4)

在rails 4中,你需要放 require File.expand_path('../../lib', __FILE__) + '/ext/string' 在你的config / application.rb

答案 8 :(得分:3)

如果您不想将异常用作逻辑的一部分,您可以尝试这样做:

class String
   def numeric?
    !!(self =~ /^-?\d+(\.\d*)?$/)
  end
end

或者,如果您希望它适用于所有对象类,请将class String替换为class Object将自我转换为字符串:!!(self.to_s =~ /^-?\d+(\.\d*)?$/)

答案 9 :(得分:3)

从Ruby 2.6.0开始,数字转换方法具有可选的exception参数[1]。这使我们能够使用内置方法,而无需将异常用作控制流:

Float('x') # => ArgumentError (invalid value for Float(): "x")
Float('x', exception: false) # => nil

因此,您不必定义自己的方法,而是可以直接检查变量,例如

if Float(my_var, exception: false)
  # do something if my_var is a float
end

答案 10 :(得分:0)

由于Kernel#Float,{{3}}可用于验证字符串的数字,我只能添加的是单线版本,而无需使用rescue块来控制流(有时被认为是不好的做法)

  Float(my_string, exception: false).present?

答案 11 :(得分:-2)

使用以下功能:

def is_numeric? val
    return val.try(:to_f).try(:to_s) == val
end

所以,

is_numeric? "1.2f" = false

is_numeric? "1.2" = true

is_numeric? "12f" = false

is_numeric? "12" = true

答案 12 :(得分:-5)

这个解决方案多么愚蠢?

def is_number?(i)
  begin
    i+0 == i
  rescue TypeError
    false
  end
end