2008-09-08 33 views
139

Mam ciąg znaków, na przykład '123', i chcę go przekonwertować na 123.Bezpieczne przetwarzanie liczb całkowitych w języku Ruby

Wiem, że można po prostu zrobić some_string.to_i, ale to zamienia 'lolipops' na 0, co nie jest efekt, który mam na myśli. Chcę, żeby wysadziło się w moją twarz, kiedy próbuję przekształcić coś nieważnego, z miłym i bolesnym Exception. W przeciwnym razie nie mogę rozróżnić między poprawnym 0 i czymś, co po prostu nie jest liczbą.

EDYCJA: Szukałem standardowego sposobu robienia tego bez podstępu regex.

+0

bezpieczeństwo trzecie !!! – jsh

Odpowiedz

207

Ruby ma tę funkcjonalność wbudowany:

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

Jak wspomniano w odpowiedzi przez Joseph Pecoraro, można oglądać na smyczki, które są ważne numery non-dziesiętne, takich jak te, zaczynając 0x na hex i 0b dla binarnych i potencjalnie bardziej trudnych liczb zaczynających się od zera, które będą analizowane jako ósemkowe.

Ruby 1.9.2 dodaje opcjonalny drugi argument dla radix więc powyższy problem można uniknąć:

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

Może to działa:

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

+1 za bycie krótkim i słodkim. –

+8

PSA: w Ruby, '^' i '$' [mają subtelnie różne znaczenia] (http://stackoverflow.com/a/5979643/1004889) jako metachary niż w większości innych smaków regexp. Prawdopodobnie chcesz użyć '\ A' i' \ Z' zamiast tego. – pje

+2

To nie zadziała dla liczb ujemnych –

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

Prawdopodobnie najczystszym sposobem, aby to zrobić , ale powinno działać.

25

Ponadto należy mieć na uwadze wpływ, że obecne rozwiązanie przyjęte, mogą mieć na parsowania szesnastkowy, ósemkowy i liczby binarne:

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

W liczbach Ruby, które zaczynają się 0x lub 0X są hex, 0b lub 0B są binarny i po prostu 0 są ósemkowe. Jeśli nie jest to pożądane zachowanie, możesz połączyć to z niektórymi innymi rozwiązaniami, które sprawdzają, czy ciąg najpierw pasuje do wzorca. Jak wyrażeń regularnych w /\d+/ itp

+1

Tego właśnie oczekiwałbym po konwersji – wvdschel

+5

W Rubim 1.9 można przekazać bazę jako drugi argument. –

1

Re: Chris's answer

rzeczy implementacji Załóżmy, takie jak "1a" lub "B2" wskroś. Jak o tym zamiast:

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 

This wyjścia:

100 
1a is invalid 
b2 is invalid 
t is invalid 
+0

Ah, dobry połów! Całkowicie przegapiłem ten łatwy błąd. –

+0

aby być pedantycznym, wzmianka o różnych kotwicach regex według @pje i używana może być niepoprawna w zależności od pożądanego zachowania. Zamiast tego rozważ użycie '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '". "" "": "". -doc.org/core-2.1.1/Regexp.html – Del

6

miałem do czynienia z tym w moim ostatnim projekcie, a moja realizacja była podobna, ale nieco inaczej:

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 
14

Kolejne nieoczekiwane zachowanie z zaakceptowanym rozwiązaniem (z wersją 1.8, 1.9 jest w porządku):

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

, więc jeśli nie masz pewności, co jest przekazywane, upewnij się, że dodano .to_s.

+5

test w Rubim 1.9. Integer (: foobar) => nie może przekonwertować symbolu na liczbę całkowitą (TypeError) – GutenYe

7

Ja lubię odpowiedź Myrona, ale cierpi na chorobę Rubinową "Nie używam już Java/C#, więc nigdy nie będę używał dziedziczenia ponownie". Otwieranie dowolnej klasy może być niebezpieczne i powinno być używane oszczędnie, , zwłaszcza, gdy jest to część głównej biblioteki Ruby. Nie mówię, że nigdy tego nie używaj, ale zazwyczaj łatwo jest tego uniknąć, a dostępne są lepsze opcje, np.

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 

Potem, gdy chcesz użyć ciąg znaków, który może być wiele to jasne, co robisz i nie sprać dowolnej klasy podstawowej, np

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. 

Możesz dodać wszelkiego rodzaju innych kontroli w initialize, jak sprawdzanie liczb binarnych itd Najważniejsze jest jednak to, że Ruby jest dla ludzi i jest dla ludzi oznacza klarowność. Nazewnictwo obiektu za pomocą jego nazwy zmiennej i powoduje, że jej nazwa klasy sprawia, że ​​obiekty są bardziej wyraźne.