Sicheres Integer-Parsing in Ruby
-
09-06-2019 - |
Frage
Ich habe zum Beispiel eine Schnur '123'
, und ich möchte es konvertieren 123
.
Ich weiß, dass du es einfach tun kannst some_string.to_i
, aber das konvertiert 'lolipops'
Zu 0
, was nicht der Effekt ist, den ich im Sinn habe.Ich möchte, dass es mir um die Ohren fliegt, wenn ich versuche, etwas Ungültiges in ein schönes und schmerzhaftes umzuwandeln Exception
.Ansonsten kann ich nicht zwischen einem gültigen unterscheiden 0
und etwas, das überhaupt keine Zahl ist.
BEARBEITEN: Ich suchte nach der Standardmethode ohne Regex-Tricks.
Lösung
Ruby verfügt über diese integrierte Funktionalität:
Integer('1001') # => 1001
Integer('1001 nights')
# ArgumentError: invalid value for Integer: "1001 nights"
Wie in der Antwort von erwähnt Joseph Pecoraro, möchten Sie möglicherweise auf Zeichenfolgen achten, bei denen es sich um gültige nichtdezimale Zahlen handelt, z. B. solche, die mit beginnen 0x
für Hex und 0b
für binäre und möglicherweise schwierigere Zahlen, die mit Null beginnen und als Oktal geparst werden.
Ruby 1.9.2 hat ein optionales zweites Argument für Radix hinzugefügt, sodass das obige Problem vermieden werden kann:
Integer('23') # => 23
Integer('0x23') # => 35
Integer('023') # => 19
Integer('0x23', 10)
# => #<ArgumentError: invalid value for Integer: "0x23">
Integer('023', 10) # => 23
Andere Tipps
Das könnte funktionieren:
i.to_i if i.match(/^\d+$/)
Beachten Sie auch die Auswirkungen, die die derzeit akzeptierte Lösung auf das Parsen von Hex-, Oktal- und Binärzahlen haben kann:
>> Integer('0x15')
# => 21
>> Integer('0b10')
# => 2
>> Integer('077')
# => 63
In Ruby-Zahlen, die mit beginnen 0x
oder 0X
sind Hex, 0b
oder 0B
sind binär und gerecht 0
sind oktal.Wenn dies nicht das gewünschte Verhalten ist, können Sie es mit einigen anderen Lösungen kombinieren, die zunächst prüfen, ob die Zeichenfolge mit einem Muster übereinstimmt.Wie /\d+/
reguläre Ausdrücke usw.
Ein weiteres unerwartetes Verhalten bei der akzeptierten Lösung (mit 1.8 ist 1.9 in Ordnung):
>> Integer(:foobar)
=> 26017
>> Integer(:yikes)
=> 26025
Wenn Sie also nicht sicher sind, was übergeben wird, stellen Sie sicher, dass Sie eine hinzufügen .to_s
.
Mir gefällt Myrons Antwort, aber er leidet an der Ruby-Krankheit „Ich verwende Java/C# nicht mehr, daher werde ich die Vererbung nie wieder verwenden.“.Das Öffnen eines Kurses kann mit Gefahren verbunden sein und sollte sparsam eingesetzt werden. besonders wenn es Teil der Kernbibliothek von Ruby ist.Ich sage nicht, dass Sie es niemals verwenden sollten, aber es ist normalerweise leicht zu vermeiden und es gibt bessere Optionen, z. B.
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
Wenn Sie dann eine Zeichenfolge verwenden möchten, die eine Zahl sein könnte, ist klar, was Sie tun, und Sie überlasten keine Kernklasse, z. B.
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.
Sie können in der Initialisierung alle möglichen anderen Prüfungen hinzufügen, z. B. die Prüfung auf Binärzahlen usw.Die Hauptsache ist jedoch, dass Ruby für Menschen da ist und es bedeutet, für Menschen zu sein Klarheit.Benennen eines Objekts über seinen Variablennamen Und Sein Klassenname macht Dinge viel klarer.
Ich musste mich in meinem letzten Projekt damit befassen und meine Implementierung war ähnlich, aber etwas anders:
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
someString = "asdfasd123"
number = someString.to_i
if someString != number.to_s
puts "oops, this isn't a number"
end
Wahrscheinlich nicht der sauberste Weg, aber es sollte funktionieren.
Re: Chris' Antwort
Ihre Implementierung lässt Dinge wie „1a“ oder „b2“ durch.Wie wäre es stattdessen damit:
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
Dies gibt aus:
100
1a is invalid
b2 is invalid
t is invalid