113 votes

Tester si une chaîne est un nombre en Ruby on Rails

J'ai les éléments suivants dans le contrôleur de mon 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 déclenche un undefined method erreur. Je suppose que j'ai défini is_number au mauvais endroit... ?

4 votes

Je sais que beaucoup de gens sont ici à cause du cours Rails for Zombies Testing de codeschool. Attends juste qu'il continue à expliquer. Les tests ne sont pas supposés réussir --- c'est normal que votre test échoue par erreur, vous pouvez toujours patcher Rails pour inventer des méthodes comme self.is_number ?

0 votes

La réponse acceptée échoue dans des cas comme "1 000" et est 39 fois plus lente que l'approche par regex. Voir ma réponse ci-dessous.

197voto

Jakob S Points 7432

Créer is_number? Méthode.

Créez une méthode d'aide :

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

Et puis appelez-le comme ça :

my_string = '12.34'

is_number?( my_string )
# => true

Étendre le site String La classe.

Si vous voulez être en mesure d'appeler is_number? directement sur la chaîne de caractères au lieu de la passer comme paramètre à votre fonction d'aide, vous devez alors définir is_number? comme une extension de la String comme ceci :

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

Et ensuite vous pouvez l'appeler avec :

my_string.is_number?
# => true

0 votes

C'est ce que je cherchais. Je ne le déclarais pas dans la classe String et je n'étais pas en mesure de l'appeler comme prévu. Merci Jakob !

3 votes

C'est une mauvaise idée. "330.346.11".to_f # => 330.346

14 votes

Il n'y a pas to_f dans l'exemple ci-dessus, et Float() ne présente pas ce comportement : Float("330.346.11") soulève ArgumentError: invalid value for Float(): "330.346.11"

32voto

hipertracker Points 1417
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 votes

/^\d+$/ n'est pas un regexp sûr en Ruby, /\A\d+\Z/ est. (par exemple, "42 \nsome texte" renverrait true )

1 votes

Pour clarifier le commentaire de @TimotheeA, il est possible d'utiliser en toute sécurité /^\d+$/ s'il s'agit de lignes, mais dans ce cas, il s'agit du début et de la fin d'une chaîne de caractères. /\A\d+\Z/ .

1 votes

Les réponses ne devraient-elles pas être éditées pour changer la réponse réelle PAR le répondant ? Changer la réponse dans une édition si vous n'êtes pas le répondant semble... peut-être sournois et devrait être hors limite.

31voto

Matt Sanders Points 1558

Voici un point de repère pour les moyens courants de résoudre ce problème. Notez que la méthode que vous devez utiliser dépend probablement du taux de faux cas attendu.

  1. S'ils sont relativement peu communs, le moulage est certainement le plus rapide.
  2. Si les faux cas sont fréquents et que vous ne vérifiez que 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 des flottants, la méthode regexp est probablement la meilleure solution.

Si les performances n'ont pas d'importance, utilisez ce que vous aimez :-)

Détails du contrôle des nombres 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 la 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

16voto

Damien MATHIEU Points 13805

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

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

1 votes

Cela ne fonctionne toutefois que pour les nombres entiers positifs. Des valeurs comme '-1', '0.0' ou '1_000' renvoient toutes un résultat faux, même si ce sont des valeurs numériques valides. Vous recherchez quelque chose comme /^[- .0-9]+$/, mais qui accepte par erreur "-". -'.

13 votes

Depuis Rails 'validates_numericality_of' : raw_value.to_s =~ / \A [+-] ? \d + \Z /

0 votes

NoMethodError : méthode non définie `should' pour "asd":String

6voto

corroded Points 9116

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

vous devriez faire is_number ?(mystring)

0 votes

D'après la méthode is_number ? de la question, l'utilisation de is_a ? ne donne pas la bonne réponse. Si mystring est en effet une chaîne de caractères, mystring.is_a?(Integer) sera toujours fausse. On dirait qu'il veut un résultat comme is_number?("12.4") #=> true

0 votes

Jakob S a raison. mystring est en effet toujours une chaîne de caractères, mais peut être composé uniquement de chiffres. peut-être que ma question aurait dû être is_numeric ? afin de ne pas confondre le type de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X