Comment tester si une chaîne est essentiellement un entier entre guillemets en utilisant Ruby


128

J'ai besoin d'une fonction is_an_integer, où

  • "12".is_an_integer? renvoie vrai.
  • "blah".is_an_integer? renvoie false.

Comment puis-je faire cela dans Ruby? J'écrirais une regex mais je suppose qu'il y a une aide pour cela dont je ne suis pas au courant.



1
Soyez prudent en utilisant des solutions reposant sur des expressions régulières. Les benchmarks montrent qu'ils s'exécutent beaucoup plus lentement que le code normal.
the Tin Man

Réponses:


135

Vous pouvez utiliser des expressions régulières. Voici la fonction avec les suggestions de @ janm.

class String
    def is_i?
       !!(self =~ /\A[-+]?[0-9]+\z/)
    end
end

Une version modifiée selon le commentaire de @wich:

class String
    def is_i?
       /\A[-+]?\d+\z/ === self
    end
end

Au cas où vous auriez seulement besoin de vérifier les nombres positifs

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  

4
Pas mal. Dans Ruby, vous omettez généralement le mot-clé "return" si la valeur de retour est générée dans la dernière expression de la fonction. Cela renverra également une valeur entière de zéro, vous voulez probablement un booléen, donc quelque chose comme !! (str = ~ / ^ [- +]? [0-9] + $ /) ferait cela. Ensuite, vous pouvez l'ajouter à String et omettre l'argument, en utilisant "self" au lieu de "str", puis vous pouvez changer le nom en "is_i?" ...
janm

2
Merci! Je n'ai absolument aucune idée des conventions et des pratiques du rubis. Je viens de faire un rapide google sur ruby ​​et les expressions régulières pour voir la syntaxe, j'ai changé l'expression régulière pour l'appliquer au problème en question et je l'ai testé. C'est assez chouette en fait ... Je devrai peut-être lui donner un look plus long quand j'aurai plus de temps libre.
Rado

Vous avez la bonne idée, mais elle ne correspond pas aux littéraux binaires ou hexadécimaux (voir ma solution modifiée ci-dessous).
Sarah Mei

16
Deux commentaires. Vous pouvez utiliser à la /regexp/ === selfplace de la !!(self =~ /regexp/)construction. Vous pouvez utiliser la classe de caractères '\ d' au lieu de[0-9]
dont le

1
La regex la plus simple pour un entier est probablement / ^ \ d + $ /
keithxm23

169

Eh bien, voici le moyen le plus simple:

class String
  def is_integer?
    self.to_i.to_s == self
  end
end

>> "12".is_integer?
=> true
>> "blah".is_integer?
=> false

Je ne suis pas d'accord avec les solutions qui provoquent une exception pour convertir la chaîne - les exceptions ne sont pas un flux de contrôle, et vous pourriez aussi bien le faire de la bonne manière. Cela dit, ma solution ci-dessus ne traite pas des entiers non en base 10. Alors, voici comment faire sans recourir à des exceptions:

  class String
    def integer? 
      [                          # In descending order of likeliness:
        /^[-+]?[1-9]([0-9]*)?$/, # decimal
        /^0[0-7]+$/,             # octal
        /^0x[0-9A-Fa-f]+$/,      # hexadecimal
        /^0b[01]+$/              # binary
      ].each do |match_pattern|
        return true if self =~ match_pattern
      end
      return false
    end
  end

27
"01" .to_i.to_s! = "01"
sepp2k

2
Tu ne pourrais pas remplacer self.to_i.to_s == selfpar Integer self rescue false?
Meredith L. Patterson

5
Vous pourriez, mais ce serait une mauvaise forme. Vous n'utilisez pas d'exceptions comme flux de contrôle et le code de personne ne doit jamais contenir "rescue false" (ou "rescue true"). Un simple gsub'ing ferait fonctionner ma solution pour les cas marginaux non spécifiés par l'OP.
Sarah Mei

4
Je sais que beaucoup de gens l'utilisent, et c'est certainement esthétique. Pour moi, cela indique que le code doit être restructuré. Si vous vous attendez à une exception ... ce n'est pas une exception.
Sarah Mei

2
Je conviens que les exceptions ne devraient pas être utilisées comme flux de contrôle. Je ne pense pas que l'exigence soit que les numéros orientés développeur soient reconnus. Dans des situations non-programmeurs, cela pourrait être considéré comme un bogue, surtout compte tenu de la confusion possible autour des zéros et de l'octal. Pas non plus cohérent avec to_i. Votre code ne gère pas le cas "-0123". Une fois que vous avez traité ce cas, vous n'avez pas besoin d'une expression régulière distincte pour octal. Vous pouvez simplement continuer en utilisant "any?". La seule instruction dans votre fonction pourrait être "[/ re1 /, / re2 /, / re3 /] .any? {| Re | self = ~ re}", sans clause if ni retour.
janvier

67

Vous pouvez utiliser Integer(str)et voir si cela augmente:

def is_num?(str)
  !!Integer(str)
rescue ArgumentError, TypeError
  false
end

Il convient de souligner que même si cela renvoie vrai pour "01", il ne le fait pas pour "09", simplement parce que 09ce ne serait pas un littéral entier valide. Si ce n'est pas le comportement souhaité, vous pouvez ajouter 10comme deuxième argument à Integer, de sorte que le nombre est toujours interprété comme base 10.


39
Mec ... provoquant une exception juste pour convertir un nombre? Les exceptions ne sont pas des flux de contrôle.
Sarah Mei

29
Ils ne le sont pas, mais malheureusement c'est la manière canonique de déterminer "l'intégralité" d'une chaîne en Ruby. Les méthodes utilisant #to_isont tout simplement trop brisées à cause de leur permissivité.
Avdi

17
Pour ceux qui se demandent pourquoi, Integer ("09") n'est pas valide car le "0" le rend octal, et 9 n'est pas un nombre octal valide. osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Andrew Grimm

20
Sarah: vous pouvez utiliser un Regex mais afin de gérer tous les cas que Ruby fait lors de l'analyse des entiers (nombres négatifs, hex, octal, traits de soulignement par exemple 1_000_000) ce serait un très gros Regex et facile de se tromper. Integer()est canonique car Integer ()vous savez avec certitude que tout ce que Ruby considère comme un entier littéral sera accepté, et tout le reste sera rejeté. Dupliquer ce que le langage vous donne déjà est sans doute une odeur de code pire que d'utiliser des exceptions pour le contrôle.
Avdi

9
@Rado So réinvente la roue.
sepp2k

24

Vous pouvez faire une seule ligne:

str = ...
int = Integer(str) rescue nil

if int
  int.times {|i| p i}
end

ou même

int = Integer(str) rescue false

En fonction de ce que vous essayez de faire, vous pouvez également utiliser directement un bloc de début de fin avec une clause de sauvetage:

begin
  str = ...
  i = Integer(str)

  i.times do |j|
    puts j
  end
rescue ArgumentError
  puts "Not an int, doing something else"
end

1
En ce qui concerne le thème «l'exception en tant que flux de contrôle»: puisque nous ne savons pas comment la méthode en question doit être utilisée, nous ne pouvons pas vraiment juger si des exceptions conviennent ou non. Si la chaîne est entrée et qu'elle doit être un entier, alors fournir un non entier justifierait une exception. Bien qu'alors peut-être que la gestion ne soit pas dans la même méthode et que nous ferions probablement simplement Integer (str) .times {| i | met i} ou autre.
Robert Klemme

24
"12".match(/^(\d)+$/)      # true
"1.2".match(/^(\d)+$/)     # false
"dfs2".match(/^(\d)+$/)    # false
"13422".match(/^(\d)+$/)   # true

4
Il ne revient pas trueet falsemais des MatchDatainstances etnil
Stefan

Ce n'est pas ce qu'il retourne, mais si cela correspond
Maciej Krasowski

5
Enveloppez-le avec !!ou utilisez-le present?si vous avez besoin d'un booléen !!( "12".match /^(\d)+$/ )ou "12".match(/^(\d)+$/).present?(ce dernier nécessitant Rails / actifsupport)
mahemoff

1
Cette expression régulière ne prend pas en compte le signe: les nombres négatifs sont également des nombres entiers valides . Vous testez maintenant des nombres naturels valides ou zéro.
Jochem Schulenklopper

13

Ruby 2.6.0 permet la conversion en un entier sans lever d'exception , et retournera nilsi la conversion échoue. Et comme nilse comporte principalement comme falsedans Ruby, vous pouvez facilement vérifier un entier comme ceci:

if Integer(my_var, exception: false)
  # do something if my_var can be cast to an integer
end

8
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. Il n'est pas préfixé par is_. Je trouve cela idiot sur les méthodes de point d'interrogation, j'aime "04".integer?beaucoup mieux que "foo".is_integer?.
  2. Il utilise la solution sensible de sepp2k, qui passe pour "01"et ainsi de suite.
  3. Orienté objet, oui.

1
+1 pour le nommer #integer ?, -1 pour encombrer String avec :-P
Avdi

1
Où irait-il autrement? integer?("a string")ftl.
August Lilleaas

2
String#integer?est le genre de patch commun que chaque codeur Ruby et son cousin aiment ajouter au langage, conduisant à des bases de code avec trois implémentations différentes subtilement incompatibles et une rupture inattendue. J'ai appris cela à la dure sur de grands projets Ruby.
Avdi

Même commentaire que ci-dessus: les exceptions ne doivent pas être utilisées pour le flux de contrôle.
Sarah Mei

Inconvénient: cette solution gaspille une conversion.
Robert Klemme

7

La meilleure et la plus simple consiste à utiliser Float

val = Float "234" rescue nil

Float "234" rescue nil #=> 234.0

Float "abc" rescue nil #=> nil

Float "234abc" rescue nil #=> nil

Float nil rescue nil #=> nil

Float "" rescue nil #=> nil

Integerc'est aussi bien mais ça reviendra 0pourInteger nil


Je suis content d'avoir remarqué votre réponse. Sinon, j'aurais choisi "Integer" quand j'avais besoin de "Float".
Jeff Zivkovic

C'est simple mais la meilleure réponse! Nous n'avons pas vraiment besoin d'un patch sophistiqué pour la classe String dans la plupart des cas. Cela fonctionne mieux pour moi!
Anh Nguyen

(Float (valeur) sauvetage faux)? Float (valeur) .to_s == valeur? Float (valeur): Entier (valeur): valeur
okliv

6

Je préfère:

config / initialiseurs / string.rb

class String
  def number?
    Integer(self).is_a?(Integer)
  rescue ArgumentError, TypeError
    false
  end
end

puis:

[218] pry(main)> "123123123".number?
=> true
[220] pry(main)> "123 123 123".gsub(/ /, '').number?
=> true
[222] pry(main)> "123 123 123".number?
=> false

ou vérifiez le numéro de téléphone:

"+34 123 456 789 2".gsub(/ /, '').number?

4

Un moyen beaucoup plus simple pourrait être

/(\D+)/.match('1221').nil? #=> true
/(\D+)/.match('1a221').nil? #=> false
/(\D+)/.match('01221').nil? #=> true

3
  def isint(str)
    return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/)
  end

Les réponses au code uniquement ne sont pas très utiles. Expliquez plutôt comment cela fonctionne et pourquoi c'est la réponse appropriée. Nous voulons éduquer pour l'avenir afin que la solution soit comprise, pas résoudre la question immédiate.
the Tin Man le

3

Personnellement, j'aime l'approche d'exception même si je la rendrais un peu plus laconique:

class String
  def integer?(str)
    !!Integer(str) rescue false
  end
end

Cependant, comme d'autres l'ont déjà indiqué, cela ne fonctionne pas avec les chaînes Octal.


2

Ruby 2.4 a Regexp#match?: (avec a ?)

def integer?(str)
  /\A[+-]?\d+\z/.match? str
end

Pour les anciennes versions de Ruby, il y a Regexp#===. Et bien que l'utilisation directe de l'opérateur d'égalité de cas devrait généralement être évitée, cela semble très clair ici:

def integer?(str)
  /\A[+-]?\d+\z/ === str
end

integer? "123"    # true
integer? "-123"   # true
integer? "+123"   # true

integer? "a123"   # false
integer? "123b"   # false
integer? "1\n2"   # false

2

Cela peut ne pas convenir à tous les cas en utilisant simplement:

"12".to_i   => 12
"blah".to_i => 0

pourrait aussi faire pour certains.

S'il s'agit d'un nombre et non de 0, il renverra un nombre. S'il renvoie 0, c'est soit une chaîne, soit 0.


11
Fonctionne mais ce n'est pas recommandé, depuis "12blah".to_i => 12. Cela pourrait causer des problèmes dans des scénarios étranges.
rfsbraz

2

Voici ma solution:

# /initializers/string.rb
class String
  IntegerRegex = /^(\d)+$/

  def integer?
    !!self.match(IntegerRegex)
  end
end

# any_model_or_controller.rb
'12345'.integer? # true
'asd34'.integer? # false

Et voici comment cela fonctionne:

  • /^(\d)+$/est une expression regex pour trouver des chiffres dans n'importe quelle chaîne. Vous pouvez tester vos expressions et résultats regex sur http://rubular.com/ .
  • Nous l'enregistrons dans une constante IntegerRegexpour éviter une allocation de mémoire inutile à chaque fois que nous l'utilisons dans la méthode.
  • integer?est une méthode interrogative qui devrait renvoyer trueou false.
  • matchest une méthode sur chaîne qui correspond aux occurrences selon l'expression regex donnée en argument et renvoie les valeurs correspondantes ou nil.
  • !!convertit le résultat de matchmethod en booléen équivalent.
  • Et déclarer la méthode dans une Stringclasse existante est une correction de singe, qui ne change rien aux fonctionnalités String existantes, mais ajoute simplement une autre méthode nommée integer?sur n'importe quel objet String.

1
Pourriez-vous ajouter une petite explication à cela s'il vous plaît?
stef

@stef - J'ai fait la même chose. S'il vous plaît laissez-moi savoir si vous avez encore des questions.
Sachin

1

En développant la réponse de @ rado ci-dessus, on pourrait également utiliser une instruction ternaire pour forcer le retour de booléens vrais ou faux sans utiliser de double frange. Certes, la version à double négation logique est plus laconique, mais probablement plus difficile à lire pour les nouveaux venus (comme moi).

class String
  def is_i?
     self =~ /\A[-+]?[0-9]+\z/ ? true : false
  end
end

Considérez que l'utilisation d'expressions régulières oblige Ruby à faire beaucoup plus de travail, donc si cela est utilisé dans une boucle, cela ralentira le code. L'ancrage de l'expression aide, mais les regex sont encore beaucoup plus lentes.
the Tin Man le

1

Pour les cas plus généralisés (y compris les nombres avec point décimal), vous pouvez essayer la méthode suivante:

def number?(obj)
  obj = obj.to_s unless obj.is_a? String
  /\A[+-]?\d+(\.[\d]+)?\z/.match(obj)
end

Vous pouvez tester cette méthode dans une session irb:

(irb)
>> number?(7)
=> #<MatchData "7" 1:nil>
>> !!number?(7)
=> true
>> number?(-Math::PI)
=> #<MatchData "-3.141592653589793" 1:".141592653589793">
>> !!number?(-Math::PI)
=> true
>> number?('hello world')
=> nil
>> !!number?('hello world')
=> false

Pour une explication détaillée de l'expression régulière impliquée ici, consultez cet article de blog :)


Il n'est pas nécessaire d'appeler obj.is_a? Stringcar String # to_s se retournera, ce qui, je suppose, ne nécessite pas trop de traitement par rapport à l' .is_a?appel. De cette façon, vous ne ferez qu'un seul appel sur cette ligne au lieu d'un ou deux. Vous pouvez également inclure directement !!dans la number?méthode, car par convention, un nom de méthode qui se termine par ?est censé renvoyer une valeur booléenne. Cordialement!
Giovanni Benussi

-1

J'aime ce qui suit, simple:

def is_integer?(str)
  str.to_i != 0 || str == '0' || str == '-0'
end

is_integer?('123')
=> true

is_integer?('sdf')
=> false

is_integer?('-123')
=> true

is_integer?('0')
=> true

is_integer?('-0')
=> true

Attention cependant:

is_integer?('123sdfsdf')
=> true

-2

Une doublure en string.rb

def is_integer?; true if Integer(self) rescue false end

-3

Je ne sais pas si c'était le cas lorsque cette question est posée, mais pour quiconque tombe sur ce post, le moyen le plus simple est:

var = "12"
var.is_a?(Integer) # returns false
var.is_a?(String) # returns true

var = 12
var.is_a?(Integer) # returns true
var.is_a?(String) # returns false

.is_a? fonctionnera avec n'importe quel objet.


Ce n'est pas ce que demande la question initiale. OP veut savoir si la chaîne est également un entier. par exemple "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == true
Marklar
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.