Безопасный разбор целочисленных значений в 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.