Análisis de enteros seguros en Ruby


Tengo una cadena, digamos '123', y quiero convertirla a 123.

Sé que puedes simplemente hacer some_string.to_i, pero eso convierte 'lolipops' a 0, que no es el efecto que tengo en mente. Quiero que explote en mi cara cuando trato de convertir algo inválido, con un Exception agradable y doloroso. De lo contrario, no puedo distinguir entre un 0 válido y algo que simplemente no es un número en absoluto.

EDIT: Estaba buscando la forma estándar de hacerlo, sin expresiones regulares engaño.

Author: the Tin Man, 2008-09-08

8 answers

Ruby tiene esta funcionalidad incorporada:

Integer('1001')                                    # => 1001  
Integer('1001 nights')  
# ArgumentError: invalid value for Integer: "1001 nights"  

Como se indica en la respuesta de Joseph Pecoraro, es posible que desee buscar cadenas que sean números no decimales válidos, como los que comienzan con 0x para hex y 0b para binario, y números potencialmente más difíciles que comienzan con cero que se analizarán como octales.

Ruby 1.9.2 agregó un segundo argumento opcional para radix, por lo que se puede evitar el problema anterior:

Integer('23')                                     # => 23
Integer('0x23')                                   # => 35
Integer('023')                                    # => 19
Integer('0x23', 10)
# => #<ArgumentError: invalid value for Integer: "0x23">
Integer('023', 10)                                # => 23
 215
Author: Slartibartfast,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:20

También tenga en cuenta los efectos que la solución aceptada actual puede tener en el análisis de números hexadecimales, octales y binarios:

>> Integer('0x15')
# => 21  
>> Integer('0b10')
# => 2  
>> Integer('077')
# => 63

En los números Ruby que comienzan con 0x o 0X son hexadecimales, 0b o 0B son binarios, y solo 0 son octales. Si este no es el comportamiento deseado, es posible que desee combinarlo con algunas de las otras soluciones que comprueban si la cadena coincide con un patrón primero. Como las expresiones regulares /\d+/, etc.

 25
Author: Joseph Pecoraro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-10 03:11:17

Esto podría funcionar:

i.to_i if i.match(/^\d+$/)
 24
Author: Purfideas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-08 18:00:44

Otro comportamiento inesperado con la solución aceptada (con 1.8, 1.9 está bien):

>> Integer(:foobar)
=> 26017
>> Integer(:yikes)
=> 26025

Así que si no está seguro de lo que se está pasando, asegúrese de agregar un .to_s.

 14
Author: Jaime Cham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-13 16:02:42

Me gusta la respuesta de Myron, pero sufre de la enfermedad de Ruby de "Ya no uso Java/C#, así que nunca volveré a usar la herencia". Abrir cualquier clase puede estar lleno de peligro y debe usarse con moderación, especialmente cuando es parte de la biblioteca central de Ruby. No estoy diciendo que nunca lo use, pero por lo general es fácil de evitar y que hay mejores opciones disponibles, por ejemplo,

class IntegerInString < String

  def initialize( s )
    fail ArgumentError, "The string '#{s}' is not an integer in a string, it's just a string." unless s =~ /^\-?[0-9]+$/
    super
  end
end

Entonces, cuando desee usar una cadena que podría ser un número, está claro lo que estás haciendo y no golpeas ninguna clase central, por ejemplo

n = IntegerInString.new "2"
n.to_i
# => 2

IntegerInString.new "blob"
ArgumentError: The string 'blob' is not an integer in a string, it's just a string.

Puede agregar todo tipo de otras comprobaciones en la inicialización, como la comprobación de números binarios, etc. Lo principal, sin embargo, es que Ruby es para las personas y ser para las personas significa claridad. Nombrar un objeto a través de su nombre de variable y su nombre de clase hace que las cosas sean mucho más claras.

 8
Author: iain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-21 15:08:34

Tuve que lidiar con esto en mi último proyecto, y mi implementación fue similar, pero un poco diferente:

class NotAnIntError < StandardError 
end

class String
  def is_int?    
    self =~ /^-?[0-9]+$/
  end

  def safe_to_i
    return self.to_i if is_int?
    raise NotAnIntError, "The string '#{self}' is not a valid integer.", caller
  end
end

class Integer
  def safe_to_i
    return self
  end            
end

class StringExtensions < Test::Unit::TestCase

  def test_is_int
    assert "98234".is_int?
    assert "-2342".is_int?
    assert "02342".is_int?
    assert !"+342".is_int?
    assert !"3-42".is_int?
    assert !"342.234".is_int?
    assert !"a342".is_int?
    assert !"342a".is_int?
  end

  def test_safe_to_i
    assert 234234 == 234234.safe_to_i
    assert 237 == "237".safe_to_i
    begin
      "a word".safe_to_i
      fail 'safe_to_i did not raise the expected error.'
    rescue NotAnIntError 
      # this is what we expect..
    end
  end

end
 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-25 06:27:33
someString = "asdfasd123"
number = someString.to_i
if someString != number.to_s
  puts "oops, this isn't a number"
end

Probablemente no sea la forma más limpia de hacerlo, pero debería funcionar.

 2
Author: Paul Wicks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-06 18:35:00

Re: La respuesta de Chris

Su implementación permite cosas como "1a" o "b2" a través. ¿Qué tal esto en su lugar:

def safeParse2(strToParse)
  if strToParse =~ /\A\d+\Z/
    strToParse.to_i
  else
    raise Exception
  end
end

["100", "1a", "b2", "t"].each do |number|
  begin
    puts safeParse2(number)
  rescue Exception
    puts "#{number} is invalid"
  end
end

Esto produce:

100
1a is invalid
b2 is invalid
t is invalid
 1
Author: metavida,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:21