Penguraian integer aman di Ruby

160

Saya memiliki string, katakanlah '123', dan saya ingin mengubahnya menjadi integer 123.

Saya tahu Anda bisa melakukannya some_string.to_i, tetapi itu berubah 'lolipops'menjadi 0, yang bukan efek yang ada dalam pikiran saya. Saya ingin itu meledak di wajah saya ketika saya mencoba mengubah sesuatu yang tidak valid, dengan yang bagus dan menyakitkan Exception. Kalau tidak, saya tidak bisa membedakan antara yang valid 0dan sesuatu yang sama sekali bukan angka.

EDIT: Saya sedang mencari cara standar untuk melakukannya, tanpa tipu daya regex.

wvdschel
sumber

Jawaban:

234

Ruby memiliki fungsi ini bawaan:

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

Seperti dicatat dalam jawaban oleh Joseph Pecoraro , Anda mungkin ingin menonton string yang merupakan angka non-desimal yang valid, seperti yang dimulai dengan 0xhex dan 0buntuk biner, dan angka yang berpotensi lebih rumit dimulai dengan nol yang akan diurai sebagai oktal.

Ruby 1.9.2 menambahkan argumen kedua opsional untuk radix sehingga masalah di atas dapat dihindari:

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

Ini mungkin berhasil:

i.to_i if i.match(/^\d+$/)
Purfideas
sumber
8
PSA: di Ruby, ^ dan $ memiliki arti yang agak berbeda sebagai metachar dari pada kebanyakan rasa regexp lainnya. Anda mungkin bermaksud menggunakan \Adan \Zsebagai gantinya.
pje
1
menjadi bertele-tele, penyebutan jangkar regex yang berbeda sesuai per @ pje mungkin salah tergantung pada perilaku yang diinginkan. Alih-alih mempertimbangkan menggunakan \zdi tempat \Zsebagai deskripsi untuk dikapitalisasi Z anchor adalah: - "Cocok mengakhiri string Jika string yang berakhir dengan baris baru, cocok sebelum baris baru." Ruby-doc.org/core-2.1.1/Regexp .html
Del
24

Perhatikan juga pengaruh yang diterima solusi saat ini pada parsing hex, octal, dan bilangan biner:

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

Dalam angka Ruby yang dimulai dengan 0xatau 0Xhex, 0batau 0Bbiner, dan hanya 0oktal. Jika ini bukan perilaku yang diinginkan, Anda mungkin ingin menggabungkannya dengan beberapa solusi lain yang memeriksa apakah string cocok dengan pola terlebih dahulu. Seperti /\d+/ungkapan reguler, dll.

Joseph Pecoraro
sumber
1
Itulah yang saya harapkan dari konversi sekalipun
wvdschel
5
Di Ruby 1.9, Anda bisa melewatkan basis sebagai argumen kedua.
Andrew Grimm
17

Perilaku tak terduga lainnya dengan solusi yang diterima (dengan 1,8, 1,9 ok):

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

jadi jika Anda tidak yakin apa yang sedang disampaikan, pastikan Anda menambahkan .to_s.

Jaime Cham
sumber
7
Tes di Ruby 1.9. Integer (: foobar) => tidak dapat mengonversi Symbol menjadi Integer (TypeError)
GutenYe
9

Saya suka jawaban Myron tetapi menderita penyakit Ruby "Saya tidak lagi menggunakan Java / C # jadi saya tidak akan pernah menggunakan warisan lagi" . Membuka kelas apa pun bisa penuh dengan bahaya dan harus digunakan dengan hemat, terutama ketika itu bagian dari perpustakaan inti Ruby. Saya tidak mengatakan jangan pernah menggunakannya, tetapi biasanya mudah dihindari dan ada pilihan yang lebih baik, misalnya

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

Kemudian ketika Anda ingin menggunakan string yang bisa berupa angka, jelas apa yang Anda lakukan dan Anda tidak mengalahkan kelas inti apa pun, misalnya

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.

Anda dapat menambahkan segala macam pemeriksaan lain di inisialisasi, seperti memeriksa angka-angka biner dll. Namun yang utama, adalah bahwa Ruby adalah untuk orang-orang dan bagi orang-orang berarti kejelasan . Memberi nama objek melalui nama variabelnya dan nama kelasnya membuat segalanya menjadi lebih jelas.

iain
sumber
6

Saya harus berurusan dengan ini dalam proyek terakhir saya, dan implementasi saya mirip, tetapi sedikit berbeda:

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

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

Mungkin bukan cara terbersih untuk melakukannya, tetapi harus berhasil.

Paul Wicks
sumber
1

Re: jawaban Chris

Implementasi Anda memungkinkan hal-hal seperti "1a" atau "b2" melalui. Bagaimana dengan ini sebagai gantinya:

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

Output ini:

100
1a is invalid
b2 is invalid
t is invalid
metavida
sumber
menjadi bertele-tele, penyebutan jangkar regex yang berbeda seperti per @ pje dan digunakan mungkin salah tergantung pada perilaku yang diinginkan. Alih-alih mempertimbangkan menggunakan \zdi tempat \Zsebagai deskripsi untuk dikapitalisasi Z anchor adalah: - "Cocok mengakhiri string Jika string yang berakhir dengan baris baru, cocok sebelum baris baru." Ruby-doc.org/core-2.1.1/Regexp .html
Del