在 RubyonRails 中测试 string 是否是一个数字

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

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

and the following condition in my controller:

if mystring.is_number?


end

条件是抛出一个 undefined method错误。我猜测我在错误的地方定义了 is_number... ?

128336 次浏览

不,你只是用错了。你的 is _ number? 有一个参数。你调用它时没有参数

你应该做的是 is _ number? (mystring)

我就是这么做的,但我也认为一定有更好的办法

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

创建 is_number?方法。

Create a helper method:

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

And then call it like this:

my_string = '12.34'


is_number?( my_string )
# => true

Extend String Class.

如果希望能够直接对字符串调用 is_number?,而不是将它作为参数传递给 helper 函数,那么需要将 is_number?定义为 String类的扩展,如下所示:

class String
def is_number?
true if Float(self) rescue false
end
end

然后你可以说:

my_string.is_number?
# => true

依赖提出的异常并不是最快、可读或可靠的解决方案。
我会这么做:

my_string.should =~ /^[0-9]+$/
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

If you prefer not to use exceptions as part of the logic, you might try this:

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

或者,如果您希望它可以跨所有对象类工作,那么将 class String替换为 class Object,并将 self 转换为字符串: !!(self.to_s =~ /^-?\d+(\.\d*)?$/)

这个解决方案有多蠢?

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

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

  1. 如果是相对不常见的铸造肯定是最快的。
  2. 如果错误情况很常见,您只是检查 int,那么比较与转换后的状态是一个很好的选择。
  3. 如果错误情况很常见,并且您正在检查 float,那么 regexp 可能是一种方法

如果性能不重要,就使用你喜欢的。 : -)

整数检查详情:

# 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

在4号轨道上,你需要把 require File.expand_path('../../lib', __FILE__) + '/ext/string' 在 config/application.rb 中

使用以下功能:

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" = 真

使用正则表达式方法。它比公认答案中的救援方法快39倍,而且还能处理“1000”这样的情况

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

--

The accepted answer by @Jakob S works for the most part, but catching exceptions can be really slow. In addition, the rescue approach fails on a string like "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

And now some test cases:

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

在 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

作为 雅各布 · S 在他的回答中建议内核 # 浮动可以用来验证字符串的数值,我只能添加一行程序版本的字符串,而不需要使用 rescue块来控制流(这有时被认为是一种不好的做法)

  Float(my_string, exception: false).present?