Ruby on Railsで文字列が数値かどうかをテストする


103

アプリケーションコントローラーに次のものがあります。

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

そして、私のコントローラーの次の状態:

if mystring.is_number?

end

条件はundefined methodエラーをスローしています。is_number間違った場所で定義したと思います...?


4
codeschoolのRails for Zombies Testingクラスのおかげで、多くの人がここにいることを知っています。彼が説明し続けるのを待ってください。テストがパスすることは想定されていません---エラーで失敗してもテストしても問題ありません。self.is_numberなどのメソッドを発明するために、いつでもレールにパッチを適用できますか?
boulder_ruby 2013

受け入れられた回答は「1,000」のような場合に失敗し、正規表現アプローチを使用するより39倍遅いです。以下の私の答えを参照してください。
pthamm 2016

回答:


186

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

2
これは悪い考えです。"330.346.11" .to_f#=> 330.346
epochwolf

11
to_f上記には何もなく、Float()はその動作を示しません。Float("330.346.11")レイズArgumentError: invalid value for Float(): "330.346.11"
ジェイコブS

7
そのパッチを使用する場合は、ルビの命名規則に沿うように、数値に名前を変更しますか(数値クラスは数値から継承し、is_プレフィックスはjavaishです)。
Konrad Reiche

10
元の質問にはあまり関係ありませんが、おそらくコードをに入れますlib/core_ext/string.rb
Jakob S

1
このis_number?(string)ビットはRuby 1.9では動作しないと思います。多分それはRailsまたは1.8の一部ですか? String.is_a?(Numeric)動作します。stackoverflow.com/questions/2095493/…も参照してください。
ロスAttrill 2014年

30

この問題に対処する一般的な方法のベンチマークは次のとおりです。どちらを使用する必要があるかは、おそらく予想される誤ったケースの比率に依存することに注意してください。

  1. それらが比較的珍しい場合は、キャストが間違いなく最速です。
  2. 偽のケースが一般的であり、intをチェックしているだけの場合は、変換された状態と比較するのが適切なオプションです。
  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

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

12
/^\d+$/Rubyでは安全な正規表現ではありません/\A\d+\Z/。(たとえば、「42 \ n何かのテキスト」はを返しますtrue
ティモシーA '1

TimotheeAさんのコメント@に明確にするために、それは使用しても安全である/^\d+$/ラインを扱う場合は、このケースでは、このように、文字列の先頭と末尾についてです/\A\d+\Z/
Julio

1
レスポンダによる実際の回答を変更するために回答を編集する必要はありませんか?あなたがレスポンダーでない場合、編集で回答を変更すると思われます...おそらく手に負えず、範囲外でなければなりません。
ジェイデル

2
\ Zでは文字列の末尾に\ nを付けることができるため、完全に数値であるかどうかに関係なく、 "123 \ n"は検証に合格します。しかし、\ zを使用すると、より正確な正規表現になります:/ \ A \ d + \ z /
SunnyMagadan 2017

15

発生した例外に依存することは、最速で、読みやすく、信頼できるソリューションではありません。
私は次のようにします:

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

1
ただし、これは正の整数に対してのみ機能します。「-1」、「0.0」、「1_000」などの値は、有効な数値であってもすべてfalseを返します。あなたは/ ^ [ -.0-9] + $ /のようなものを見ていますが、それは誤って「 --」を受け入れます
Jakob S

13
Rails 'validates_numericality_of'から:raw_value.to_s =〜/ \ A [+-]?\ d + \ Z /
Morten

NoMethodError: `すべきである」 "ASD"のための未定義のメソッド:文字列
sergserg

最新のrspecでは、次のようになりますexpect(my_string).to match(/^[0-9]+$/)
Damien MATHIEU 2014年

私は好きです:my_string =~ /\A-?(\d+)?\.?\d+\Z/「.1」、「-0.1」、または「12」を実行できますが、「」、「-」、「。」は実行できません。
Josh

8

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

7

これが私のやり方ですが、私ももっと良い方法があるはずだと思います

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

5
1.2e + 35などの浮動表記は認識されません。
ハイパートラッカー2012

1
Ruby 2.4.0で実行object = "1.2e+35"; object.to_f.to_s == objectしたところ、うまくいきました
Giovanni Benussi

6

いいえ、間違って使用しています。あなたのis_number?議論があります。引数なしで呼び出しました

is_numberを実行する必要がありますか?(mystring)


is_numberに基づいていますか?問題のメソッド、is_a?正しい答えを与えていません。mystringが実際に文字列の場合、mystring.is_a?(Integer)常にfalseになります。彼は次のような結果を望んでいるようですis_number?("12.4") #=> true
ヤコブS

ヤコブSは正しいです。mystringは確かに常に文字列ですが、単なる数字で構成されている場合もあります。おそらく私の質問はis_numericである必要がありますか?データ型を混同しないように
ジェイミーブキャナン

6

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

ベンチマークをありがとう。受け入れられた回答には、などの入力を受け入れるという利点があり5.4e-29ます。私はあなたの正規表現がそれらも受け入れるように調整されると思います。
ジョディ

3
1,000のようなケースの処理は、ユーザーの意図に依存するため、非常に困難です。人間が数字をフォーマットする方法はたくさんあります。1,000は1000とほぼ同じですか、それとも1とほぼ同じですか?世界のほとんどは、それが、1についての整数1000を表示する方法ではないと言う
ジェームズ・ムーア

4

Rails 4ではrequire File.expand_path('../../lib', __FILE__) + '/ext/string' 、config / application.rb を配置する必要があり ます


1
実際にはこれを行う必要はありません。 "initializers"にstring.rbを置くだけで機能します。
mahatmanich 2014年

3

ロジックの一部として例外を使用したくない場合は、これを試すことができます。

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

それとも、あなたはすべてのオブジェクトクラス全体で仕事にそれをしたい場合は、交換するclass Stringclass Object文字列に変換する自己: !!(self.to_s =~ /^-?\d+(\.\d*)?$/)


否定してnil?ゼロにすることの目的はルビーでは不法なので、あなたはちょうど行うことができます!!(self =~ /^-?\d+(\.\d*)?$/)
アーノルド・ロア

!!確かに使用できます。少なくとも1つのRubyスタイルガイド(github.com/bbatsov/ruby-style-guide!!.nil?、読みやすさを優先して回避することを提案してい!!ますが、人気のあるリポジトリで使用されているのを見たことがあり、ブール値に変換するための素晴らしい方法だと思います。回答を編集しました。
Mark Schneider

-3

次の関数を使用します。

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

そう、

is_numeric? "1.2f" = false

is_numeric? "1.2" =真

is_numeric? "12f" = false

is_numeric? "12" =真


valがの場合、これは失敗し"0"ます。また、このメソッド.tryはRubyコアライブラリの一部ではなく、ActiveSupportが含まれている場合にのみ使用できることにも注意してください。
GMA

実際、それはでも失敗する"12"ので、この質問の4番目の例は間違っています。"12.10"そして"12.00"失敗も。
GMA

-5

このソリューションはどれほど馬鹿げていますか?

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

1
'.respond_to?(:+)'を使用すると、特定のメソッド(:+)呼び出しで失敗して例外をキャッチするよりも常に優れているため、これは最適ではありません。これは、Regexや変換メソッドにはないさまざまな理由で失敗する可能性もあります。
Sqeaky 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.