Cómo probar si una cadena es básicamente un número entero entre comillas usando Ruby

128

Necesito una función is_an_integer, donde

  • "12".is_an_integer? devuelve verdadero
  • "blah".is_an_integer? devuelve falso

¿Cómo puedo hacer esto en Ruby? Escribiría una expresión regular pero supongo que hay un ayudante para esto que no conozco.

Tony
fuente
1
Tenga cuidado al usar soluciones basadas en expresiones regulares. Los puntos de referencia muestran que se ejecutan mucho más lentamente que el código normal.
The Tin Man

Respuestas:

135

Puedes usar expresiones regulares. Aquí está la función con las sugerencias de @ janm.

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

Una versión editada según el comentario de @wich:

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

En caso de que solo necesite verificar números positivos

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  
Rado
fuente
44
No está mal. En Ruby, generalmente se omite la palabra clave "return" si el valor de retorno se genera en la última expresión de la función. Esto también devolverá un valor entero de cero, probablemente desee un valor booleano, así que algo como !! (str = ~ / ^ [- +]? [0-9] + $ /) haría eso. Luego, podría agregarlo a String y omitir el argumento, usando "self" en lugar de "str", y luego podría cambiar el nombre a "is_i?" ...
enero
2
¡Gracias! No tengo ni idea de las convenciones y prácticas de rubí. Acabo de hacer un rápido google en ruby ​​y expresiones regulares para ver la sintaxis, cambié la expresión regular para aplicar al problema en cuestión y lo probé. Es bastante bueno en realidad ... Puede que tenga que darle una mirada más larga cuando tengo más tiempo libre.
Rado
Tienes la idea correcta, pero no coincide con los literales binarios o hexadecimales (ver mi solución editada a continuación).
Sarah Mei
16
Dos comentarios Puede usar en /regexp/ === selflugar de la !!(self =~ /regexp/)construcción. Puede usar la clase de caracteres '\ d' en lugar de[0-9]
que el
1
La expresión regular más simple para un entero probablemente sea / ^ \ d + $ /
keithxm23
169

Bueno, aquí está la manera fácil:

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

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

No estoy de acuerdo con las soluciones que provocan una excepción para convertir la cadena: las excepciones no son el flujo de control, y es mejor que lo haga de la manera correcta. Dicho esto, mi solución anterior no trata con enteros que no son de base 10. Así que aquí está la manera de hacerlo sin recurrir a excepciones:

  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
Sarah Mei
fuente
27
"01" .to_i.to_s! = "01"
sepp2k
2
¿No podrías reemplazarlo self.to_i.to_s == selfcon Integer self rescue false?
Meredith L. Patterson
55
Podrías, pero eso sería una mala forma. No utiliza excepciones como flujo de control, y el código de nadie debe contener "rescate falso" (o "rescate verdadero"). Algunos gsub'ing simples harían que mi solución funcione para casos extremos no especificados por el OP.
Sarah Mei
44
Sé que mucha gente lo usa, y ciertamente es estéticamente agradable. Para mí, sin embargo, es una indicación de que el código necesita una reestructuración. Si espera una excepción ... no es una excepción.
Sarah Mei
2
Estoy de acuerdo en que las excepciones no deben usarse como flujo de control. No creo que el requisito sea que se reconozcan los números orientados al desarrollador. En situaciones que no son del programador, esto podría verse como un error, especialmente dado que existe una posible confusión sobre los ceros iniciales y el octal. Tampoco es consistente con to_i. Su código no maneja el caso "-0123". Una vez que maneja ese caso, no necesita una expresión regular separada para octal. Puedes simplemente avanzar usando "any?". La única declaración en su función podría ser "[/ re1 /, / re2 /, / re3 /] .any? {| Re | self = ~ re}", sin cláusulas if o retornos.
enero
67

Puede usar Integer(str)y ver si aumenta:

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

Cabe señalar que, si bien esto devuelve verdadero para "01", no lo es para "09", simplemente porque 09no sería un literal entero válido. Si ese no es el comportamiento que desea, puede agregarlo 10como un segundo argumento para Integerque el número siempre se interprete como base 10.

sepp2k
fuente
39
Amigo ... provocando una excepción solo para convertir un número? Las excepciones no son control de flujo.
Sarah Mei
29
No lo son, pero desafortunadamente esta es la forma canónica de determinar el "entero" de una cadena en Ruby. Los métodos que usan #to_iestán demasiado rotos debido a su permisividad.
Avdi
17
Para aquellos que se preguntan por qué, Integer ("09") no es válido porque el "0" lo hace octal, y 9 no es un número octal válido. osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Andrew Grimm
20
Sarah: puede usar una expresión regular, pero para manejar todos los casos que Ruby hace al analizar enteros (números negativos, hexadecimales, octales, guiones bajos, por ejemplo, 1_000_000) sería una expresión regular muy grande y fácil de equivocar. Integer()es canónico porque con Integer ()usted sabe con certeza que cualquier cosa que Ruby considere un literal entero será aceptada, y todo lo demás será rechazado. Duplicar lo que el lenguaje ya le brinda es posiblemente un olor a código peor que usar excepciones para el control.
Avdi
9
@Rado So está reinventando la rueda.
sepp2k
24

Puedes hacer un trazador de líneas:

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

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

o incluso

int = Integer(str) rescue false

Dependiendo de lo que intente hacer, también puede usar directamente un bloque de inicio y final con cláusula de rescate:

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

  i.times do |j|
    puts j
  end
rescue ArgumentError
  puts "Not an int, doing something else"
end
Robert Klemme
fuente
1
Con respecto al tema "excepción como flujo de control": dado que no sabemos cómo se va a utilizar el método en cuestión, no podemos juzgar si las excepciones encajarían o no. Si se ingresa la cadena y se requiere que sea un número entero, entonces proporcionar un número entero garantizaría una excepción. Aunque entonces tal vez el manejo no está en el mismo método y probablemente solo haríamos Integer (str) .times {| i | pone i} o lo que sea.
Robert Klemme
24
"12".match(/^(\d)+$/)      # true
"1.2".match(/^(\d)+$/)     # false
"dfs2".match(/^(\d)+$/)    # false
"13422".match(/^(\d)+$/)   # true
Maciej Krasowski
fuente
44
No vuelve truey falsesino MatchDatacasos ynil
Stefan
No es lo que devuelve, pero si coincide
Maciej Krasowski
55
Envuélvalo !!o úselo present?si necesita un booleano !!( "12".match /^(\d)+$/ )o "12".match(/^(\d)+$/).present?(este último requiere Rails / soporte activo)
mahemoff
1
Esta expresión regular no tiene en cuenta un signo: los números negativos también son números enteros válidos . Ahora está probando números naturales válidos o cero.
Jochem Schulenklopper
8
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. No tiene el prefijo is_. Me parece tonto en los métodos de interrogación, me gusta "04".integer?mucho más que "foo".is_integer?.
  2. Utiliza la solución sensata de sepp2k, que pasa por "01"y tal.
  3. Orientado a objetos, yay.
August Lilleaas
fuente
1
+1 por nombrarlo #integer ?, -1 por desordenar Cadena con él
:-P
1
¿A dónde más iría? integer?("a string")ftl.
August Lilleaas
2
String#integer?es el tipo de parche común que a cada codificador de Ruby y su primo le gusta agregar al lenguaje, lo que lleva a bases de código con tres implementaciones sutilmente incompatibles y una rotura inesperada. Aprendí esto de la manera difícil en grandes proyectos de Ruby.
Avdi
Mismo comentario que el anterior: las excepciones no deben usarse para el flujo de control.
Sarah Mei
Desventaja: esta solución está desperdiciando una conversión.
Robert Klemme
7

La mejor y más simple forma es usar 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

Integertambién es bueno pero volverá 0porInteger nil

Siva
fuente
Me alegro de haber notado tu respuesta. De lo contrario, habría ido con "Integer" cuando necesitaba "Float".
Jeff Zivkovic
¡Esto es simple pero la mejor respuesta! Realmente no necesitamos un parche elegante para la clase String en la mayoría de los casos. ¡Esto funciona mejor para mí!
Anh Nguyen
(Flotador (valor) rescate falso)? Float (value) .to_s == value? Float (valor): Entero (valor): valor
okliv
6

Yo prefiero:

config / initializers / string.rb

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

y entonces:

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

o verifica el número de teléfono:

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

Una forma mucho más simple podría ser

/(\D+)/.match('1221').nil? #=> true
/(\D+)/.match('1a221').nil? #=> false
/(\D+)/.match('01221').nil? #=> true
gouravtiwari21
fuente
3
  def isint(str)
    return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/)
  end
Amal Kumar S
fuente
Las respuestas de solo código no son muy útiles. En su lugar, proporcione una explicación de cómo funciona y por qué es la respuesta adecuada. Queremos educar para el futuro para que se entienda la solución, no para resolver la pregunta inmediata.
The Tin Man
3

Personalmente, me gusta el enfoque de excepción, aunque lo haría un poco más conciso:

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

Sin embargo, como ya han dicho otros, esto no funciona con cadenas Octal.

ochobitraptor
fuente
2

Ruby 2.4 tiene Regexp#match?: (con a ?)

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

Para versiones anteriores de Ruby, hay Regexp#===. Y aunque generalmente se debe evitar el uso directo del operador de igualdad de casos, aquí se ve muy limpio:

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
Stefan
fuente
2

Esto podría no ser adecuado para todos los casos simplemente usando:

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

también podría hacer para algunos.

Si es un número y no 0, devolverá un número. Si devuelve 0, es una cadena o 0.

Tres
fuente
11
Funciona pero no es recomendable, ya que "12blah".to_i => 12. Esto podría causar algunos problemas en escenarios extraños.
rfsbraz
2

Aquí está mi solución:

# /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

Y así es como funciona:

  • /^(\d)+$/es una expresión regular para encontrar dígitos en cualquier cadena. Puede probar sus expresiones y resultados de expresiones regulares en http://rubular.com/ .
  • Lo IntegerRegexguardamos en una constante para evitar la asignación innecesaria de memoria cada vez que lo usamos en el método.
  • integer?es un método interrogativo que debería devolver trueo false.
  • matches un método en cadena que coincide con las ocurrencias según la expresión regex dada en el argumento y devuelve los valores coincidentes o nil.
  • !!convierte el resultado del matchmétodo en booleano equivalente.
  • Y declarar el método en la Stringclase existente es parchear mono, que no cambia nada en las funcionalidades de String existentes, sino que simplemente agrega otro método nombrado integer?en cualquier objeto String.
Sachin
fuente
1
¿Podría agregar una pequeña explicación a esto por favor?
stef
@stef - He hecho lo mismo. Avíseme si aún tiene alguna consulta.
Sachin
1

Ampliando la respuesta de @ rado anterior, también se podría usar una declaración ternaria para forzar el retorno de booleanos verdaderos o falsos sin el uso de doble explosión. De acuerdo, la versión de doble negación lógica es más concisa, pero probablemente más difícil de leer para los recién llegados (como yo).

class String
  def is_i?
     self =~ /\A[-+]?[0-9]+\z/ ? true : false
  end
end
adeluccar
fuente
Considere que el uso de expresiones regulares obliga a Ruby a hacer mucho más trabajo, por lo que si se usa en un bucle, ralentizará el código. Anclar la expresión ayuda, pero las expresiones regulares siguen siendo significativamente más lentas.
The Tin Man
1

Para casos más generalizados (incluidos números con punto decimal), puede probar el siguiente método:

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

Puede probar este método en una sesión 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

Para obtener una explicación detallada de la expresión regular involucrada aquí, consulte este artículo del blog :)

Taiga
fuente
No es necesario llamar obj.is_a? Stringporque String # to_s se devolverá, lo que supongo que no requiere demasiado procesamiento en comparación con la .is_a?llamada. De esta manera, solo realizará una llamada en esta línea en lugar de una o dos. Además, podría incluir directamente !!dentro del number?método, porque por convención, ?se supone que un nombre de método que termina devuelve un valor booleano. ¡Saludos!
Giovanni Benussi
-1

Me gusta lo siguiente, directo:

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

Cuidado sin embargo:

is_integer?('123sdfsdf')
=> true
schmijos
fuente
-2

Un trazador de líneas en string.rb

def is_integer?; true if Integer(self) rescue false end
cb24
fuente
-3

No estoy seguro de si esto sucedió cuando se hizo esta pregunta, pero para cualquiera que se encuentre con esta publicación, la forma más simple es:

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? funcionará con cualquier objeto.

Novato
fuente
Esa no es la pregunta original. OP quiere saber si la cadena es un número entero también. por ejemplo "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == true
Marklar el