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.
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
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.
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+$/)
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
.
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.
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
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.
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
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
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