web-dev-qa-db-fra.com

Teste si la chaîne est un nombre entre Ruby sur Rails

J'ai les éléments suivants dans mon contrôleur d'application:

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

et la condition suivante dans mon contrôleur:

if mystring.is_number?

end

La condition jette un undefined method Erreur. Je suppose que j'ai défini is_number au mauvais endroit ...?

95
Jamie Buchanan

Créer is_number? Méthode.

Créez une méthode d'assistance:

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

Et puis appelez ça comme ça:

my_string = '12.34'

is_number?( my_string )
# => true

Étendre String Classe.

Si vous voulez pouvoir appeler is_number? directement sur la chaîne au lieu de la passer comme paramètre à votre fonction d’aide, vous devez définir is_number? comme une extension de la classe String, comme ceci:

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

Et puis vous pouvez l'appeler avec:

my_string.is_number?
# => true
178
Jakob S
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
29
hipertracker

Voici une référence pour les moyens courants de résoudre ce problème. Notez que celui que vous devriez utiliser dépend probablement du nombre de faux cas attendus.

  1. Si elles sont relativement rares, le casting est définitivement le plus rapide.
  2. Si les cas erronés sont fréquents et que vous ne faites que vérifier les ints, la comparaison avec un état transformé est une bonne option.
  3. Si les faux cas sont fréquents et que vous vérifiez les flottants, l’expression rationnelle est probablement la voie à suivre.

Si les performances ne comptent pas, utilisez ce que vous aimez. :-)

Détails de vérification entiers:

# 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

Détails de vérification du flotteur:

# 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
26
Matt Sanders

S'appuyer sur l'exception levée n'est pas la solution la plus rapide, lisible ni fiable.
Je ferais ce qui suit:

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

Tl; dr: Utilisez une approche regex. Il est 39 fois plus rapide que l'approche de secours dans la réponse acceptée et traite également des cas du type "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

-

La réponse acceptée par @Jobob S fonctionne pour la plupart, mais la détection des exceptions peut être très lente. En outre, l'approche de secours échoue sur une chaîne telle que "1 000".

Définissons les méthodes:

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

Et maintenant quelques cas de test:

test_cases = {
  true => ["5.5", "23", "-123", "1,234,123"],
  false => ["hello", "99designs", "(123)456-7890"]
}

Et un peu de code pour exécuter les cas de test:

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

Voici le résultat des cas de test:

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

Il est temps de faire quelques points de repère de performance:

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

Et les résultats:

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
pthamm

non, vous l'utilisez mal. votre is_number? a un argument. vous l'avez appelé sans argument

vous devriez faire is_number? (mystring)

6
corroded

Dans Rails 4, vous devez mettre require File.expand_path('../../lib', __FILE__) + '/ext/string' dans votre config/application.rb

4
jcye

voici comment je le fais, mais je pense aussi qu'il doit y avoir un meilleur moyen

object.to_i.to_s == object || object.to_f.to_s == object
4
antpaw

Si vous préférez ne pas utiliser les exceptions dans le cadre de la logique, essayez ceci:

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

Ou, si vous souhaitez que cela fonctionne sur toutes les classes d'objets, remplacez class String Par class Object Et convertissez-le en chaîne: !!(self.to_s =~ /^-?\d+(\.\d*)?$/)

3
Mark Schneider

À partir de Ruby 2.6.0, les méthodes de conversion numériques ont un argument optionnel exception- [1] . Cela nous permet d'utiliser le -in méthodes sans utiliser d'exceptions comme flux de contrôle:

Float('x') # => ArgumentError (invalid value for Float(): "x")
Float('x', exception: false) # => nil

Par conséquent, vous n'avez pas à définir votre propre méthode, mais vous pouvez directement vérifier des variables telles que, par exemple.

if Float(my_var, exception: false)
  # do something if my_var is a float
end
3
Timitry