在Ruby中安全地解析整数

169

我有一个字符串'123',我想将它转换为整数123

我知道你可以简单地使用some_string.to_i进行转换,但是这会将'lolipops'转换为0,这不是我想要的效果。我希望当我尝试转换无效的内容时它会抛出一个漂亮而痛苦的异常。否则,我无法区分有效的0和根本不是数字的东西。

编辑: 我在寻找标准的方法,而不是使用正则表达式技巧。

8个回答

243

Ruby内置了这个功能:

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

Joseph Pecoraro在回答中所指出的,您可能需要注意有效的非十进制字符串,例如以0x表示十六进制和以0b表示二进制的字符串,以及可能更棘手的以零开头将被解析为八进制的数字。

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

33

这可能有效:

i.to_i if i.match(/^\d+$/)

9
提示:在 Ruby 中,^$ 作为元字符与大多数其他正则表达式风格不同,具有微妙的不同含义。你可能想使用 \A\Z 代替。 - pje
2
说实话,根据 @pje 的建议提到不同的正则表达式锚点可能是不正确的,这取决于所需的行为。相反,考虑使用 \z 替换 \Z,因为大写字母 Z 锚点的描述是:“匹配字符串的结尾。如果字符串以换行符结尾,则匹配换行符之前”。--http://ruby-doc.org/core-2.1.1/Regexp.html - Del

25

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

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

在Ruby中,以0x0X开头的数字是十六进制,以0b0B开头的数字是二进制,以0开头的数字是八进制。如果这不是您所期望的行为,您可能需要将其与一些其他解决方案相结合,首先检查字符串是否匹配某个模式,比如/\d+/正则表达式等。


1
这正是我期望的转换结果。 - wvdschel
6
在Ruby 1.9中,你可以将基数作为第二个参数传递。 - Andrew Grimm

17

使用被接受的解决方案仍然存在另一个意外行为(在1.8中,1.9可以正常工作):

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

如果你不确定传递了什么值,确保添加 .to_s


8
在Ruby 1.9中进行测试。Integer(:foobar) => 无法将Symbol转换为Integer(TypeError) - GutenYe

9
我喜欢Myron的答案,但它遭受了Ruby病毒的困扰("我不再使用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是为人类服务的,这意味着清晰度。通过使用变量名和类名来命名对象可以使事情更加明确。


7

我在上一个项目中也遇到了这个问题,我的实现方式类似但有些不同:

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

4
someString = "asdfasd123"
number = someString.to_i
if someString != number.to_s
  puts "oops, this isn't a number"
end

这可能不是最干净的方法,但应该可以工作。


1

回复: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

说实话,根据@pje提供的不同正则表达式锚点的描述,使用可能是不正确的,这取决于所需的行为。相反,考虑在\Z的位置使用\z,因为大写字母Z锚点的描述是:“匹配字符串的结尾。如果字符串以换行符结尾,则匹配换行符之前”--http://ruby-doc.org/core-2.1.1/Regexp.html - Del

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接