Analisi di numeri interi sicuri in Ruby


160

Ho una stringa, diciamo '123', e voglio convertirla nell'intero 123.

So che si può fare semplicemente some_string.to_i, ma che converte 'lolipops'a 0, che non è l'effetto che ho in mente. Voglio che mi esploda in faccia quando provo a convertire qualcosa di invalido, con un aspetto piacevole e doloroso Exception. Altrimenti, non riesco a distinguere tra un valido 0e qualcosa che non è affatto un numero.

EDIT: stavo cercando il modo standard di farlo, senza inganno regex.

Risposte:


234

Ruby ha questa funzionalità integrata:

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

Come osservato nella risposta di Joseph Pecoraro , potresti voler cercare stringhe che sono numeri non decimali validi, come quelli che iniziano con 0xhex e 0bbinary, e numeri potenzialmente più difficili che iniziano con zero che verranno analizzati come ottali.

Ruby 1.9.2 ha aggiunto il secondo argomento opzionale per radix, quindi il problema sopra riportato può essere evitato:

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

27

Questo potrebbe funzionare:

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

8
PSA: in Ruby, ^e $ hanno significati leggermente diversi come metachars rispetto alla maggior parte degli altri sapori regexp. Probabilmente intendi usare \Ae \Zinvece.
pje

1
per essere pedante, la menzione di diverse ancore regex come per @pje può essere errata a seconda del comportamento desiderato. Invece considera l'uso \zal posto di \Zcome la descrizione per l'ancora Z maiuscola è: "Corrisponde alla fine della stringa. Se la stringa termina con una nuova riga, corrisponde appena prima della nuova riga" - ruby-doc.org/core-2.1.1/Regexp .html
Del

24

Inoltre, tenere presente gli effetti che l'attuale soluzione accettata potrebbe avere sull'analisi dei numeri esadecimali, ottali e binari:

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

In Ruby i numeri che iniziano con 0xo 0Xsono esadecimali 0bo 0Bsono binari e 0sono solo ottali. Se questo non è il comportamento desiderato, potresti voler combinarlo con alcune delle altre soluzioni che controllano se la stringa corrisponde prima a un modello. Come le /\d+/espressioni regolari, ecc.


1
Questo è quello che mi aspetterei dalla conversione
wvdschel,

5
In Ruby 1.9, puoi passare la base come secondo argomento.
Andrew Grimm,

17

Un altro comportamento imprevisto con la soluzione accettata (con 1.8, 1.9 è ok):

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

quindi se non sei sicuro di ciò che viene passato, assicurati di aggiungere un .to_s.


7
test in Ruby 1.9. Integer (: foobar) => impossibile convertire Symbol in Integer (TypeError)
GutenYe

9

Mi piace la risposta di Myron ma soffre della malattia di Ruby di "Non uso più Java / C #, quindi non userò mai più l'ereditarietà" . L'apertura di qualsiasi classe può essere piena di pericoli e dovrebbe essere usata con parsimonia, specialmente quando fa parte della biblioteca principale di Ruby. Non sto dicendo di non usarlo mai, ma di solito è facile da evitare e che ci sono opzioni migliori disponibili, ad es

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

Quindi, quando desideri utilizzare una stringa che potrebbe essere un numero, è chiaro cosa stai facendo e non ostruisci alcuna classe principale, ad es.

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.

È possibile aggiungere tutti i tipi di altri controlli nell'inizializzazione, come il controllo di numeri binari ecc. La cosa principale, tuttavia, è che Ruby è per le persone ed essere per le persone significa chiarezza . La denominazione di un oggetto tramite il nome della variabile e il nome della sua classe rende le cose molto più chiare.


6

Ho dovuto occuparmene nel mio ultimo progetto e la mia implementazione era simile, ma un po 'diversa:

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

Probabilmente non è il modo più pulito per farlo, ma dovrebbe funzionare.


1

Ri: la risposta di Chris

La tua implementazione permette di fare cose come "1a" o "b2". Che ne dici invece di questo:

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

Questo produce:

100
1a is invalid
b2 is invalid
t is invalid

per essere pedanti, la menzione di diverse ancore regex come da @pje e usate può essere errata a seconda del comportamento desiderato. Invece considera l'uso \zal posto di \Zcome la descrizione per l'ancora Z maiuscola è: "Corrisponde alla fine della stringa. Se la stringa termina con una nuova riga, corrisponde appena prima della nuova riga" - ruby-doc.org/core-2.1.1/Regexp .html
Del
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.