การแยกจำนวนเต็มอย่างปลอดภัยใน Ruby


160

ฉันมีสตริงพูดและผมต้องการที่จะแปลงเป็นจำนวนเต็ม'123'123

ฉันรู้ว่าคุณสามารถทำsome_string.to_iแต่แปลงที่'lolipops'ไป0ซึ่งไม่เป็นผลที่ผมมีในใจ Exceptionฉันต้องการที่จะระเบิดขึ้นในใบหน้าของฉันเมื่อฉันพยายามที่จะแปลงบางสิ่งบางอย่างที่ไม่ถูกต้องกับความสุขและความเจ็บปวด มิฉะนั้นฉันไม่สามารถแยกความแตกต่างระหว่างสิ่งที่ถูกต้อง0กับสิ่งที่ไม่ได้เป็นตัวเลขเลย

แก้ไข:ฉันกำลังมองหาวิธีการมาตรฐานในการทำโดยไม่มีกลอุบาย regex

คำตอบ:


234

Ruby มีฟังก์ชันนี้ในตัว:

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

ตามที่ระบุไว้ในคำตอบโดยJoseph Pecoraroคุณอาจต้องการดูสตริงที่เป็นตัวเลขที่ไม่ใช่ทศนิยมที่ถูกต้องเช่นสตริงที่ขึ้นต้นด้วย0xhex และ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 ^และ$ มีความหมายที่แตกต่างกันอย่างละเอียดว่าเป็น metachars มากกว่าในรสชาติ regexp อื่น ๆ ส่วนใหญ่ คุณอาจหมายถึงการใช้\Aและ\Zแทน
pje

1
เป็น pedantic การกล่าวถึงของ regex anchors ต่าง ๆ ตาม @ pje อาจไม่ถูกต้องขึ้นอยู่กับพฤติกรรมที่ต้องการ ให้พิจารณาใช้\zแทน\Zคำอธิบายสำหรับตัวยึด Z ตัวพิมพ์ใหญ่แทน: "จับคู่ส่วนท้ายของสตริงถ้าสตริงลงท้ายด้วย newline มันจะจับคู่ก่อนขึ้นบรรทัดใหม่" - ruby-doc.org/core-2.1.1/Regexp .html
Del

24

นอกจากนี้ควรระวังผลกระทบที่โซลูชันที่ยอมรับในปัจจุบันอาจมีการแยกวิเคราะห์เลขฐานสิบหกฐานแปดและเลขฐานสอง:

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

ในหมายเลข Ruby ที่ขึ้นต้นด้วย0xหรือ0Xเป็น hex 0bหรือ0Bเป็น binary และเพิ่ง0เป็นฐานแปด หากนี่ไม่ใช่พฤติกรรมที่ต้องการคุณอาจต้องการรวมเข้ากับโซลูชันอื่น ๆ ที่ตรวจสอบว่าสตริงตรงกับรูปแบบก่อนหรือไม่ เหมือน/\d+/นิพจน์ทั่วไป ฯลฯ


1
นั่นคือสิ่งที่ผมคาดหวังจากการแปลงแม้ว่า
wvdschel

5
ใน Ruby 1.9 คุณสามารถส่งผ่านฐานเป็นอาร์กิวเมนต์ที่สอง
Andrew Grimm

17

อีกพฤติกรรมที่ไม่คาดคิดกับโซลูชันที่ยอมรับ (ด้วย 1.8, 1.9 ก็โอเค)

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

.to_sดังนั้นหากคุณกำลังไม่แน่ใจว่าสิ่งที่จะถูกส่งผ่านไปในให้แน่ใจว่าคุณเพิ่ม


7
ทดสอบใน Ruby 1.9 จำนวนเต็ม (: foobar) => ไม่สามารถแปลง Symbol เป็น Integer (TypeError)
GutenYe

9

ผมชอบคำตอบของไมรอน แต่มันทนทุกข์ทรมานจากโรคทับทิม"ผมไม่ได้ใช้ Java / C # ดังนั้นฉันไม่เคยไปใช้มรดกอีกครั้ง" การเปิดคลาสใด ๆ อาจเต็มไปด้วยอันตรายและควรใช้อย่าง จำกัดโดยเฉพาะเมื่อเป็นส่วนหนึ่งของห้องสมุดหลักของรูบี้ ฉันไม่ได้บอกว่าไม่เคยใช้มัน แต่มักจะหลีกเลี่ยงได้ง่ายและมีตัวเลือกที่ดีกว่าเช่น

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.

คุณสามารถเพิ่มทุกประเภทของการตรวจสอบอื่น ๆ ในการเริ่มต้นเช่นการตรวจสอบเลขฐานสอง ฯลฯ สิ่งสำคัญ แต่เป็นที่ทับทิมเป็นคนและเป็นสำหรับคนที่หมายถึงความชัดเจน การตั้งชื่อวัตถุด้วยชื่อตัวแปรและชื่อคลาสทำให้สิ่งต่างชัดเจนขึ้น


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

เป็น pedantic การกล่าวถึงของ regex anchors ต่าง ๆ ตาม @ pje และการใช้อาจไม่ถูกต้องขึ้นอยู่กับพฤติกรรมที่ต้องการ ให้พิจารณาใช้\zแทน\Zคำอธิบายสำหรับตัวยึด Z ตัวพิมพ์ใหญ่แทน: "จับคู่ส่วนท้ายของสตริงถ้าสตริงลงท้ายด้วย newline มันจะจับคู่ก่อนขึ้นบรรทัดใหม่" - ruby-doc.org/core-2.1.1/Regexp .html
Del
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.