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

답변:


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")raisesArgumentError: invalid value for Float(): "330.346.11"
Jakob S

7
해당 패치를 사용하는 경우 Ruby 이름 지정 규칙에 따라 이름을 numeric?
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

30

다음은이 문제를 해결하는 일반적인 방법에 대한 벤치 마크입니다. 어느 것을 사용해야할지는 예상되는 잘못된 사례의 비율에 따라 달라질 수 있습니다.

  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

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)
티모시

@TimotheeA의 주석을 명확히하기 위해 /^\d+$/줄을 다룰 때 사용하는 것이 안전 하지만이 경우 문자열의 시작과 끝에 관한 것이므로 /\A\d+\Z/.
Julio

1
응답자의 실제 답변을 변경하기 위해 답변을 편집하면 안 되나요? 응답자가 아닌 경우 편집에서 답변을 변경하는 것 같습니다.
jaydel

2
\ Z는 문자열 끝에 \ n을 포함 할 수 있으므로 "123 \ n"은 완전한 숫자가 아니더라도 유효성 검사를 통과합니다. 당신이 \ Z를 사용한다면 그것은 더 정확한 정규 표현식 될 것입니다 : / \ v \ D + \ Z /
SunnyMagadan

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]+$/)
데미안 마티유에게

나는 좋아한다 : 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)을 인식하지 않습니다.
hipertracker 2012-07-25

1
루비 2.4.0에서 나는 실행 object = "1.2e+35"; object.to_f.to_s == object그리고 일
조반니 Benussi에게

6

아니 당신은 그것을 잘못 사용하고 있습니다. 당신의 is_number? 논쟁이 있습니다. 당신은 논쟁없이 그것을 불렀다

is_number를해야합니까? (mystring)


is_number를 기반으로? 질문의 방법, is_a 사용? 정답을 제공하지 않습니다. 경우 mystring문자열이 참으로, mystring.is_a?(Integer)항상 false가 될 것입니다. 그는 다음과 같은 결과를 원하는 것 같습니다is_number?("12.4") #=> true
Jakob S

Jakob S가 맞습니다. mystring은 실제로 항상 문자열이지만 숫자로만 구성 될 수 있습니다. 아마도 내 질문은 is_numeric이어야 했습니까? 데이터 유형을 혼동하지 않도록
Jamie Buchanan

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. 나는 당신의 정규식이 그것들을 수용하도록 조정할 수 있다고 생각합니다.
Jodi

3
사용자의 의도에 따라 다르기 때문에 1,000 건과 같은 사례를 처리하는 것은 정말 어렵습니다. 인간이 숫자를 형식화하는 방법에는 여러 가지가 있습니다. 1,000은 대략 1000과 같습니까, 아니면 대략 1과 같습니까? 세계의 대부분은 정수 1000 보여주는 방법에 대한 1, 아니다 말한다
제임스 무어

4

Rails 4에서는 require File.expand_path('../../lib', __FILE__) + '/ext/string' config / application.rb 를 입력해야 합니다.


1
실제로이 작업을 수행 할 필요가 없습니다. "initializers"에 string.rb를 넣을 수 있습니다. 그러면 작동합니다!
mahatmanich

3

논리의 일부로 예외를 사용하지 않으려면 다음을 시도 할 수 있습니다.

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

모든 객체 클래스에서 작동하도록하려는 경우 또는 교체 class String와 함께 class Object문자열로 변환 자기 : !!(self.to_s =~ /^-?\d+(\.\d*)?$/)


무슨 부정의 목적과 어떻게 nil?당신은 그냥 할 수 있도록 제로 루비에 trurthy이며,!!(self =~ /^-?\d+(\.\d*)?$/)
아놀드 로아

사용 !!확실히하는 것은 작동합니다. 적어도 하나의 루비 스타일 가이드 ( 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" = 거짓

is_numeric? "1.2" = 참

is_numeric? "12f" = 거짓

is_numeric? "12" = 참


val이이면 실패합니다 "0". 또한이 메서드 .try는 Ruby 핵심 라이브러리의 일부가 아니며 ActiveSupport를 포함하는 경우에만 사용할 수 있습니다.
GMA

실제로에도 실패하므로이 "12"질문의 네 번째 예는 잘못되었습니다. "12.10"그리고 "12.00"실패합니다.
GMA

-5

이 솔루션은 얼마나 멍청합니까?

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

1
'.respond_to? (: +)'를 사용하는 것이 항상 특정 메서드 (: +) 호출에서 실패하고 예외를 포착하는 것보다 낫기 때문에 이는 차선책입니다. Regex 및 변환 방법이 그렇지 않은 다양한 이유로 인해 실패 할 수도 있습니다.
Sqeaky
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.