Ruby'de güvenli tamsayı ayrıştırma


160

Diyelim '123', bir dize var ve tamsayıya dönüştürmek istiyorum 123.

Ben sadece bunu biliyorum some_string.to_i, ama dönüştüren 'lolipops'için 0aklımda var etki değildir. Geçersiz bir şeyi hoş ve acı verici bir şekilde dönüştürmeye çalıştığımda yüzümde patlamasını istiyorum Exception. Aksi takdirde, geçerli 0bir sayı ile hiç sayı olmayan bir şeyi ayırt edemem .

EDIT: Regex hile olmadan, bunu standart bir yol arıyordum.

Yanıtlar:


234

Ruby bu işlevselliğe yerleşik olarak sahiptir:

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

Joseph Pecoraro'nun cevabında belirtildiği gibi, 0xonaltılık ve 0bikili için başlayanlar gibi ondalık olmayan geçerli sayılar olan ve sıfır ile başlayan ve sekizli olarak ayrıştırılacak potansiyel olarak daha zor sayılar olan dizeleri izlemek isteyebilirsiniz .

Ruby 1.9.2, yukarıdaki sayıdan kaçınılması için sayı tabanı için isteğe bağlı ikinci argüman ekledi:

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

27

Bu işe yarayabilir:

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

8
PSA: Ruby'de ^ve $ metakharlar gibi diğer regexp lezzetlerinden daha farklı anlamlara sahiptir. Muhtemelen kullanmak istersiniz \Ave \Zbunun yerine.
pje

1
bilgiçlikçi olmak için, @pje'ye göre farklı regex çapalarından bahsetmek istenen davranışa bağlı olarak yanlış olabilir. Bunun yerine kullanmayı düşünün \zyerine \Zharfle Z çapası açıklamasıdır olarak: - "Eşleşmeler dizesinin sonuna bir satır ile dize uçları, sadece yeni satır önce eşleşirse." Ruby-doc.org/core-2.1.1/Regexp .html
Del

24

Ayrıca, kabul edilen mevcut çözümün onaltılık, sekizlik ve ikili sayıları ayrıştırma üzerindeki etkilerinin farkında olun:

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

Ruby ile başlayan 0xveya 0Xonaltılık, ikili 0bveya 0Bsadece 0sekizli sayılar . Bu istenen davranış değilse, dizenin bir kalıpla eşleşip eşleşmediğini kontrol eden diğer çözümlerle birleştirmek isteyebilirsiniz. Gibi /\d+/vb düzenli ifadeler,


1
Yine de dönüşümden bekleyebileceğim şey bu
wvdschel

5
Ruby 1.9'da, üssü ikinci bir argüman olarak iletebilirsiniz.
Andrew Grimm

17

Kabul edilen çözümle beklenmeyen başka bir davranış (1.8, 1.9 ile tamam):

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

bu yüzden neyin iletildiğinden emin değilseniz, bir eklediğinizden emin olun .to_s.


7
Ruby 1.9'da test edin. Tamsayı (: foobar) => Sembolü
Tamsayıya

9

Myron'un cevabını seviyorum ama "Artık Java / C # kullanmıyorum, bir daha asla miras kullanmayacağım" Ruby hastalığından muzdarip . Herhangi bir sınıfın açılması tehlikeyle dolu olabilir ve özellikle Ruby'nin temel kütüphanesinin bir parçası olduğunda az miktarda kullanılmalıdır . Hiç kullanmayın demiyorum, ama genellikle kaçınmak kolaydır ve daha iyi seçenekler vardır, örneğin

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

Sonra bir sayı olabilecek bir dize kullanmak istediğinizde ne yaptığınız açıktır ve herhangi bir çekirdek sınıfı tıkamazsınız, örn.

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.

Başlangıçta ikili sayıları kontrol etmek gibi diğer kontrolleri ekleyebilirsiniz. Ancak asıl önemli olan Ruby insanlar içindir ve insanlar için açıklık demektir . Bir nesneyi değişken adı ve sınıf adı aracılığıyla adlandırmak , işleri daha net hale getirir .


6

Son projemde bununla başa çıkmak zorunda kaldım ve uygulamam benzerdi, ancak biraz farklıydı:

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

Muhtemelen bunu yapmanın en temiz yolu değil, ama çalışması gerekir.


1

Re: Chris'in cevabı

Uygulamanız "1a" veya "b2" gibi şeyleri yapalım. Bunun yerine:

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

Bu çıktılar:

100
1a is invalid
b2 is invalid
t is invalid

bilgiçlikçi olmak için, @pje'ye göre ve kullanılan farklı regex ankrajlarından bahsetmek istenen davranışa bağlı olarak yanlış olabilir. Bunun yerine kullanmayı düşünün \zyerine \Zharfle Z çapası açıklamasıdır olarak: - "Eşleşmeler dizesinin sonuna bir satır ile dize uçları, sadece yeni satır önce eşleşirse." Ruby-doc.org/core-2.1.1/Regexp .html
Del
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.