Проверьте, является ли строка числом в Ruby on Rails - PullRequest
95 голосов
/ 14 апреля 2011

В моем контроллере приложения есть следующее:

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

и следующее условие в моем контроллере:

if mystring.is_number?

end

Условие выдает ошибку undefined method. Я предполагаю, что определил is_number не в том месте ...?

Ответы [ 12 ]

178 голосов
/ 14 апреля 2011

Создать 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
29 голосов
/ 25 июля 2012
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
26 голосов
/ 08 ноября 2013

Вот эталон для распространенных способов решения этой проблемы. Обратите внимание, что вы должны использовать, вероятно, зависит от ожидаемого числа ложных случаев.

  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
14 голосов
/ 14 апреля 2011

Опора на возникшее исключение - не самое быстрое, читаемое и надежное решение.
Я бы сделал следующее:

my_string.should =~ /^[0-9]+$/
6 голосов
/ 20 февраля 2016

Tl; др: Используйте подход регулярного выражения. Это в 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

-

Принятый ответ @Jakob S работает по большей части, но отлов исключений может быть очень медленным. Кроме того, спасательный подход не работает на строке типа «1000».

Давайте определим методы:

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 голосов
/ 14 апреля 2011

нет, вы просто используете это неправильно. ваш is_number? есть аргумент. Вы назвали это без аргумента

вы должны делать is_number? (Mystring)

4 голосов
/ 19 мая 2014

В рельсах 4 нужно поставить require File.expand_path('../../lib', __FILE__) + '/ext/string' в вашем config / application.rb

4 голосов
/ 14 апреля 2011

это то, как я это делаю, но я тоже думаю, что должен быть лучший способ

object.to_i.to_s == object || object.to_f.to_s == object
3 голосов
/ 03 января 2019

Начиная с 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
3 голосов
/ 07 июня 2013

Если вы предпочитаете не использовать исключения как часть логики, вы можете попробовать это:

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

Или, если вы хотите, чтобы он работал на всех классах объектов, замените class String на class Object преобразовать себя в строку: !!(self.to_s =~ /^-?\d+(\.\d*)?$/)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...