Phân tích số nguyên an toàn trong Ruby


160

Tôi có một chuỗi, nói '123'và tôi muốn chuyển đổi nó thành số nguyên 123.

Tôi biết bạn chỉ có thể làm some_string.to_i, nhưng điều đó chuyển 'lolipops'thành 0, đó không phải là hiệu ứng tôi có trong tâm trí. Tôi muốn nó nổ tung vào mặt tôi khi tôi cố gắng chuyển đổi một cái gì đó không hợp lệ, với một điều tốt đẹp và đau đớn Exception. Mặt khác, tôi không thể phân biệt giữa một số hợp lệ 0và một cái gì đó hoàn toàn không phải là một con số.

EDIT: Tôi đang tìm kiếm cách làm tiêu chuẩn, không có mánh khóe regex.

Câu trả lời:


234

Ruby có chức năng này được tích hợp sẵn trong:

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

Như đã lưu ý trong câu trả lời của Joseph Pecoraro , bạn có thể muốn xem các chuỗi có số không thập phân hợp lệ, chẳng hạn như các số bắt đầu bằng 0xhex và 0bcho nhị phân, và các số khó hơn có thể bắt đầu bằng số 0 sẽ được phân tích thành số bát phân.

Ruby 1.9.2 đã thêm đối số thứ hai tùy chọn cho cơ số để có thể tránh được vấn đề trên:

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

27

Điều này có thể làm việc:

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

8
PSA: trong Ruby, ^$ có ý nghĩa khác nhau một cách tinh tế như metachar so với hầu hết các hương vị regrec khác. Bạn có thể có nghĩa là để sử dụng \A\Zthay vào đó.
pje

1
là phạm vi, việc đề cập đến các neo regex khác nhau theo @pje có thể không chính xác tùy thuộc vào hành vi mong muốn. Thay vào đó, hãy xem xét sử dụng \zthay \Zcho mô tả cho neo Z được viết hoa là: "Khớp kết thúc chuỗi. Nếu chuỗi kết thúc bằng một dòng mới, nó khớp ngay trước dòng mới" - ruby-doc.org/core-2.1.1/Regapi .html
Del

24

Ngoài ra, hãy lưu ý đến những ảnh hưởng mà giải pháp được chấp nhận hiện tại có thể có khi phân tích cú pháp các số hex, bát phân và nhị phân:

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

Trong các số Ruby bắt đầu bằng 0xhoặc 0Xlà hex, 0bhoặc 0Blà nhị phân, và chỉ 0là số bát phân. Nếu đây không phải là hành vi mong muốn, bạn có thể muốn kết hợp điều đó với một số giải pháp khác để kiểm tra xem chuỗi có khớp với mẫu trước không. Giống như các /\d+/biểu thức thông thường, vv


1
Đó là những gì tôi mong đợi từ việc chuyển đổi mặc dù
wvdschel

5
Trong Ruby 1.9, bạn có thể chuyển cơ sở làm đối số thứ hai.
Andrew Grimm

17

Một hành vi bất ngờ khác với giải pháp được chấp nhận (với 1.8, 1.9 là ok):

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

Vì vậy, nếu bạn không chắc chắn những gì đang được truyền vào, hãy đảm bảo bạn thêm một .to_s.


7
thử nghiệm trong Ruby 1.9. Số nguyên (: foobar) => không thể chuyển đổi Biểu tượng thành Số nguyên (TypeError)
GutenYe

9

Tôi thích câu trả lời của Myron nhưng nó mắc phải căn bệnh Ruby "Tôi không còn sử dụng Java / C # nữa nên tôi sẽ không bao giờ sử dụng quyền thừa kế nữa" . Mở bất kỳ lớp học nào cũng có thể gây nguy hiểm và nên được sử dụng một cách tiết kiệm, đặc biệt khi đó là một phần của thư viện cốt lõi của Ruby. Tôi không nói là đừng bao giờ sử dụng nó, nhưng nó thường dễ tránh và có những lựa chọn tốt hơn, vd

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

Sau đó, khi bạn muốn sử dụng một chuỗi có thể là một số, bạn sẽ rõ những gì bạn đang làm và bạn không ghi đè bất kỳ lớp lõi nào, ví dụ:

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.

Bạn có thể thêm tất cả các loại kiểm tra khác vào lúc khởi tạo, như kiểm tra số nhị phân, v.v. Điều chính yếu là Ruby dành cho mọi người và đối với mọi người có nghĩa là rõ ràng . Đặt tên một đối tượng thông qua tên biến tên lớp của nó làm cho mọi thứ rõ ràng hơn nhiều .


6

Tôi đã phải đối phó với điều này trong dự án cuối cùng của tôi và việc thực hiện của tôi cũng tương tự, nhưng hơi khác một chút:

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

Có lẽ không phải là cách sạch nhất để làm điều đó, nhưng nên làm việc.


1

Re: Câu trả lời của Chris

Việc triển khai của bạn cho phép những thứ như "1a" hoặc "b2" thông qua. Làm thế nào về điều này thay thế:

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

Kết quả này:

100
1a is invalid
b2 is invalid
t is invalid

là phạm vi, việc đề cập đến các neo regex khác nhau theo @pje và được sử dụng có thể không chính xác tùy thuộc vào hành vi mong muốn. Thay vào đó, hãy xem xét sử dụng \zthay \Zcho mô tả cho neo Z được viết hoa là: "Khớp kết thúc chuỗi. Nếu chuỗi kết thúc bằng một dòng mới, nó khớp ngay trước dòng mới" - ruby-doc.org/core-2.1.1/Regapi .html
Del
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.