Безопасный разбор целочисленных значений в Ruby


160

Скажем '123', у меня есть строка, и я хочу преобразовать ее в целое число 123.

Я знаю , что вы можете просто сделать some_string.to_i, но обращенные 'lolipops'к 0, который не является эффект , который я имею в виду. Я хочу, чтобы это взорвалось мне в лицо, когда я пытаюсь преобразовать что-то недействительное, с хорошим и болезненным Exception. В противном случае, я не могу различить действительное 0и то, что просто не является числом вообще.

РЕДАКТИРОВАТЬ: Я искал стандартный способ сделать это, без обмана регулярных выражений.

Ответы:


234

В Ruby встроена эта функциональность:

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

Как отметил в ответе Джозеф Пекораро , вы можете захотеть следить за строками, которые являются допустимыми недесятичными числами, такими как строки, начинающиеся с 0xшестнадцатеричных и 0bдвоичных чисел, и потенциально более хитрые числа, начинающиеся с нуля, которые будут проанализированы как восьмеричные.

В Ruby 1.9.2 добавлен необязательный второй аргумент для radix, поэтому вышеупомянутой проблемы можно избежать:

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

27

Это может сработать:

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

8
PSA: в Ruby, ^и $ имеют несколько иное значение как метачары, чем в большинстве других разновидностей регулярных выражений. Вы, вероятно, хотите использовать \Aи \Zвместо.
Пье

1
чтобы быть педантичным, упоминание различных якорей регулярных выражений в соответствии с @pje может быть неправильным в зависимости от желаемого поведения. Вместо этого рассмотрите возможность использования \zвместо того, чтобы \Zв качестве описания для якоря Z с заглавной буквы использовалось следующее: «Соответствует концу строки. Если строка заканчивается новой строкой, она совпадает непосредственно перед новой строкой
Del

24

Также следует учитывать влияние, которое текущее принятое решение может оказать на синтаксический анализ шестнадцатеричных, восьмеричных и двоичных чисел:

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

В Ruby числа, которые начинаются с 0xили 0Xявляются шестнадцатеричными, 0bили 0Bдвоичными, и просто 0восьмеричными. Если это нежелательное поведение, вы можете объединить его с некоторыми другими решениями, которые проверяют, соответствует ли строка шаблону. Как /\d+/регулярные выражения и т. Д.


1
Это то, что я ожидал от преобразования, хотя
wvdschel

5
В Ruby 1.9 вы можете передать базу в качестве второго аргумента.
Эндрю Гримм

17

Еще одно неожиданное поведение с принятым решением (с 1.8, 1.9 в порядке):

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

так что если вы не уверены, что именно передается, обязательно добавьте .to_s.


7
тест в Ruby 1.9. Integer (: foobar) => не может преобразовать Symbol в Integer (TypeError)
GutenYe

9

Мне нравится ответ Мирона, но он страдает от болезни Руби "Я больше не использую 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 предназначен для людей, а наличие для людей означает ясность . Присвоение имени объекта через его имя переменной и ее имя класса делает вещи гораздо яснее.


6

Мне пришлось иметь дело с этим в моем последнем проекте, и моя реализация была похожей, но немного другой:

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

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

Вероятно, не самый чистый способ сделать это, но должен работать.


1

Re: Крис ответ

Ваша реализация позволяет вещам типа «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 с заглавной буквы использовалось следующее: «Соответствует концу строки. Если строка заканчивается новой строкой, она совпадает непосредственно перед новой строкой
Del
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.