簡體   English   中英

測試字符串是否是 Ruby on Rails 中的數字

[英]Test if string is a number in Ruby on Rails

我的應用程序控制器中有以下內容:

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

以及我的控制器中的以下條件:

if mystring.is_number?

end

條件是拋出undefined method錯誤。 我猜我在錯誤的地方定義了is_number ......?

創建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? 作為String類的擴展,如下所示:

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

然后你可以調用它:

my_string.is_number?
# => true
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

這是解決此問題的常用方法的基准。 請注意您應該使用哪一個可能取決於預期的錯誤案例的比率。

  1. 如果是比較不常見的施法肯定是最快的。
  2. 如果錯誤情況很常見並且您只是檢查整數,則比較與轉換狀態是一個不錯的選擇。
  3. 如果錯誤情況很常見並且您正在檢查浮點數,則正則表達式可能是要走的路

如果性能無關緊要,請使用您喜歡的。 :-)

整數檢查細節:

# 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

從 Ruby 2.6.0 開始,數字轉換方法有一個可選的exception -argument [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

依賴引發的異常不是最快、可讀或可靠的解決方案。
我會做以下事情:

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

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

這就是我的做法,但我也認為一定有更好的方法

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

不,你只是用錯了。 你的is_number? 有一個論點。 你在沒有參數的情況下調用它

你應該做 is_number?(mystring)

在 Rails 4 中,您需要在 config/application.rb 中放置require File.expand_path('../../lib', __FILE__) + '/ext/string'

如果您不想將異常用作邏輯的一部分,您可以試試這個:

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

或者,如果您希望它適用於所有對象類,請將class String替換為class Object並將 self 轉換為字符串: !!(self.to_s =~ /^-?\\d+(\\.\\d*)?$/)

正如Jakob S 在他的回答中所建議的Kernel#Float可用於驗證字符串的數字性,我唯一可以添加的是它的單行版本,而不使用rescue塊來控制流(這被認為是一種不好的做法有時)

  Float(my_string, exception: false).present?

使用以下函數:

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

所以,

is_numeric? "1.2f" is_numeric? "1.2f" =假

is_numeric? "1.2" is_numeric? "1.2" =真

is_numeric? "12f" is_numeric? "12f" =假

is_numeric? "12" is_numeric? "12" = 真

這個解決方案有多愚蠢?

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM