Ruby 中的安全整数解析

我有一个字符串,比如 '123',我想把它转换成整数 123

我知道你可以简单地做 some_string.to_i,但这转换 'lolipops'0,这不是我想的效果。我想它爆炸在我的脸时,我试图转换一些无效的东西,与一个不错的和痛苦的 Exception。否则,我无法区分有效的 0和根本不是数字的东西。

编辑: 我一直在寻找标准的方法来做这件事,没有正则表达式的诡计。

96441 次浏览

Ruby 内置了以下功能:

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

正如在 Joseph Pecoraro的回答中指出的,您可能希望查看有效的非十进制数字的字符串,比如以 0x开头的十六进制数字和以 0b开头的二进制数字,以及可能更棘手的以0开头的数字,这些数字将被解析为八进制数字。

Ruby 1.9.2为基数添加了可选的第二个参数,因此可以避免上述问题:

Integer('23')                                     # => 23
Integer('0x23')                                   # => 35
Integer('023')                                    # => 19
Integer('0x23', 10)
# => #<ArgumentError: invalid value for Integer: "0x23">
Integer('023', 10)                                # => 23

这可能会奏效:

i.to_i if i.match(/^\d+$/)
someString = "asdfasd123"
number = someString.to_i
if someString != number.to_s
puts "oops, this isn't a number"
end

可能不是最干净的方法,但应该有用。

还要注意当前可接受的解决方案可能对解析十六进制、八进制和二进制数产生的影响:

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

0x0X开头的 Ruby 数字是十六进制,0b0B是二进制,而 0是八进制。如果这不是您想要的行为,那么您可能需要将它与其他一些解决方案结合起来,这些解决方案首先检查字符串是否与模式匹配。像 /\d+/正则表达式等等。

回复: Chris 的回答

你的实现让“1a”或“ b2”这样的东西通过。这样如何:

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

产出:

100
1a is invalid
b2 is invalid
t is invalid

在上一个项目中,我不得不处理这个问题,我的实现方式与之类似,但有一点不同:

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

接受解决方案的另一个意外行为(使用1.8和1.9是可以的) :

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

因此,如果您不确定传入的是什么,请确保添加 .to_s

我喜欢迈伦的回答,但它患有 “我不再使用 Java/C # ,所以我再也不会使用继承”红宝石病。打开任何类都可能充满危险,当 尤其是是 Ruby 核心库的一部分时,应该谨慎使用。我不是说永远不要使用它,但它通常很容易避免,并且有更好的选择,例如。

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

然后,当您希望使用一个可以是数字的字符串时,就可以清楚地看到您在做什么,并且不会重击任何核心类,例如。

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.

您可以在初始化中添加所有类型的其他检查,比如检查二进制数等。不过最重要的是,Ruby 是为人服务的,而为人服务意味着 清晰。通过变量名 还有来命名一个对象,它的类名可以让事情变得更清楚。