Come verificare se una stringa è sostanzialmente un numero intero tra virgolette usando Ruby


128

Ho bisogno di una funzione is_an_integer, dove

  • "12".is_an_integer? ritorna vero.
  • "blah".is_an_integer? restituisce false.

Come posso farlo in Ruby? Scriverei una regex ma suppongo che ci sia un aiuto per questo di cui non sono a conoscenza.



1
Prestare attenzione utilizzando soluzioni basate su espressioni regolari. I benchmark mostrano che funzionano molto più lentamente del codice normale.
Tin Man,

Risposte:


135

Puoi usare espressioni regolari. Ecco la funzione con i suggerimenti di @ janm.

class String
    def is_i?
       !!(self =~ /\A[-+]?[0-9]+\z/)
    end
end

Una versione modificata secondo il commento di @wich:

class String
    def is_i?
       /\A[-+]?\d+\z/ === self
    end
end

Nel caso in cui devi solo controllare i numeri positivi

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  

4
Non male. In Ruby di solito si omette la parola chiave "return" se il valore restituito viene generato nell'ultima espressione nella funzione. Questo restituirà anche un valore intero pari a zero, probabilmente vuoi un valore booleano, quindi qualcosa come !! (str = ~ / ^ [- +]? [0-9] + $ /) lo farebbe. Quindi potresti aggiungerlo a String ed escludere l'argomento, usando "self" invece di "str", e quindi puoi cambiare il nome in "is_i?" ...
Jan

2
Grazie! Non ho assolutamente idea di convenzioni e pratiche sul rubino. Ho appena fatto un rapido google su ruby ​​ed espressioni regolari per vedere la sintassi, ho cambiato la regex per applicarla al problema in questione e l'ho testata. In realtà è abbastanza pulito .. Potrei doverlo guardare più a lungo quando avrò più tempo libero.
Rado

Hai l'idea giusta, ma non corrisponde a valori letterali binari o esadecimali (vedi la mia soluzione modificata di seguito).
Sarah Mei,

16
Due commenti Puoi usare al /regexp/ === selfposto del !!(self =~ /regexp/)costrutto. Puoi usare la classe di caratteri '\ d' invece di[0-9]
che il

1
La regex più semplice per un numero intero è probabilmente / ^ \ d + $ /
keithxm23

169

Bene, ecco il modo semplice:

class String
  def is_integer?
    self.to_i.to_s == self
  end
end

>> "12".is_integer?
=> true
>> "blah".is_integer?
=> false

Non sono d'accordo con le soluzioni che provocano un'eccezione per convertire la stringa: le eccezioni non sono il flusso di controllo e potresti anche farlo nel modo giusto. Detto questo, la mia soluzione sopra non si occupa di numeri interi non base-10. Quindi, ecco il modo di fare senza ricorrere alle eccezioni:

  class String
    def integer? 
      [                          # In descending order of likeliness:
        /^[-+]?[1-9]([0-9]*)?$/, # decimal
        /^0[0-7]+$/,             # octal
        /^0x[0-9A-Fa-f]+$/,      # hexadecimal
        /^0b[01]+$/              # binary
      ].each do |match_pattern|
        return true if self =~ match_pattern
      end
      return false
    end
  end

27
"01" .to_i.to_s! = "01"
sepp2k il

2
Non potresti sostituirlo self.to_i.to_s == selfcon Integer self rescue false?
Meredith L. Patterson,

5
Potresti, ma sarebbe una cattiva forma. Non usi le eccezioni come flusso di controllo e nessun codice di nessuno dovrebbe mai contenere "save false" (o "rescue true"). Qualche semplice gsub farebbe funzionare la mia soluzione per casi limite non specificati dall'OP.
Sarah Mei,

4
So che molte persone lo usano ed è certamente esteticamente piacevole. Per me però è un'indicazione che il codice necessita di ristrutturazione. Se ti aspetti un'eccezione ... non è un'eccezione.
Sarah Mei,

2
Concordo sul fatto che le eccezioni non debbano essere utilizzate come flusso di controllo. Non credo che il requisito sia il riconoscimento dei numeri orientati agli sviluppatori. In situazioni non programmatore che potrebbero essere viste come un bug, soprattutto vista la possibile confusione attorno agli zeri e all'ottale. Inoltre non coerente con to_i. Il tuo codice non gestisce il caso "-0123". Una volta che gestisci quel caso, non hai bisogno di una regexp separata per ottale. Puoi semplicemente ulteriormente utilizzando "qualsiasi?". L'unica istruzione nella tua funzione potrebbe essere "[/ re1 /, / re2 /, / re3 /] .any? {| Re | self = ~ re}", senza clausole if o return.
Jan

67

Puoi usare Integer(str)e vedere se genera:

def is_num?(str)
  !!Integer(str)
rescue ArgumentError, TypeError
  false
end

Va sottolineato che mentre ciò ritorna vero per "01", non per "09", semplicemente perché 09non sarebbe un intero valido letterale. Se questo non è il comportamento che desideri, puoi aggiungere 10come secondo argomento Integer, quindi il numero viene sempre interpretato come base 10.


39
Amico ... provocando un'eccezione solo per convertire un numero? Le eccezioni non sono il flusso di controllo.
Sarah Mei,

29
Non lo sono, ma sfortunatamente questo è il modo canonico per determinare "l'integrità" di una stringa in Ruby. I metodi che usano #to_isono semplicemente troppo rotti a causa della sua permissività.
Avdi,

17
Per quelli che si chiedono perché, Integer ("09") non è valido perché lo "0" lo rende ottale e 9 non è un numero ottale valido. osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Andrew Grimm,

20
Sarah: puoi usare un Regex ma per gestire tutti i casi che Ruby fa quando analizza numeri interi (numeri negativi, esadecimali, ottali, trattini bassi, ad esempio 1_000_000) sarebbe un Regex molto grande e facile sbagliare. Integer()è canonico perché con Integer ()te sai con certezza che tutto ciò che Ruby considera letterale intero verrà accettato e tutto il resto verrà rifiutato. Duplicare ciò che la lingua ti dà già è probabilmente un odore di codice peggiore rispetto all'utilizzo delle eccezioni per il controllo.
Avdi,

9
@Rado Quindi sta reinventando la ruota.
sepp2k,

24

Puoi fare una fodera:

str = ...
int = Integer(str) rescue nil

if int
  int.times {|i| p i}
end

o anche

int = Integer(str) rescue false

A seconda di ciò che stai cercando di fare, puoi anche utilizzare direttamente un blocco di inizio e fine con la clausola di salvataggio:

begin
  str = ...
  i = Integer(str)

  i.times do |j|
    puts j
  end
rescue ArgumentError
  puts "Not an int, doing something else"
end

1
Per quanto riguarda l'argomento "eccezione come flusso di controllo": poiché non sappiamo come utilizzare il metodo a portata di mano, non possiamo davvero giudicare se le eccezioni si adatteranno o meno. Se la stringa viene immessa ed è necessario che sia un numero intero, fornire un non numero intero richiederebbe un'eccezione. Anche se forse la gestione non è nello stesso metodo e probabilmente faremmo solo Integer (str) .times {| i | mette i} o altro.
Robert Klemme,

24
"12".match(/^(\d)+$/)      # true
"1.2".match(/^(\d)+$/)     # false
"dfs2".match(/^(\d)+$/)    # false
"13422".match(/^(\d)+$/)   # true

4
Non restituisce truee false, ma MatchDatale istanze enil
Stefan

Non è ciò che restituisce, ma se corrisponde
Maciej Krasowski l'

5
Avvolgerlo con !!o l'uso present?se avete bisogno di un valore booleano !!( "12".match /^(\d)+$/ )o "12".match(/^(\d)+$/).present?(questi ultimi richiedono Rails / ActiveSupport)
mahemoff

1
Questa espressione regolare non tiene conto di un segno: anche i numeri negativi sono numeri interi validi . Ora stai testando numeri naturali validi o zero.
Jochem Schulenklopper,

13

Ruby 2.6.0 consente il cast su un numero intero senza sollevare un'eccezione e restituirà nilse il cast fallisce. E dal momento che nilsi comporta principalmente come falsein Ruby, puoi facilmente verificare un intero in questo modo:

if Integer(my_var, exception: false)
  # do something if my_var can be cast to an integer
end

8
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. Non ha il prefisso is_. Lo trovo stupido sui metodi del punto interrogativo, mi piace "04".integer?molto meglio di "foo".is_integer?.
  2. Usa la soluzione sensibile di sepp2k, che passa per "01"e così.
  3. Orientato agli oggetti, yay.

1
+1 per nominarlo #integer ?, -1 per ingombrare String con esso :-P
Avdi

1
Dove altro andrebbe? integer?("a string")FTL.
August Lilleaas,

2
String#integer?è il tipo di patch comune che ad ogni programmatore di Ruby e ai loro cugini piace aggiungere al linguaggio, portando a basi di codice con tre diverse implementazioni sottilmente incompatibili e rotture inaspettate. L'ho imparato a mie spese su grandi progetti Ruby.
Avdi,

Stesso commento di cui sopra: le eccezioni non devono essere utilizzate per il flusso di controllo.
Sarah Mei,

Unico inconveniente: questa soluzione sta sprecando una conversione.
Robert Klemme,

7

Il modo migliore e semplice sta usando Float

val = Float "234" rescue nil

Float "234" rescue nil #=> 234.0

Float "abc" rescue nil #=> nil

Float "234abc" rescue nil #=> nil

Float nil rescue nil #=> nil

Float "" rescue nil #=> nil

Integerè anche buono ma tornerà 0perInteger nil


Sono contento di aver notato la tua risposta. Altrimenti sarei andato con "Integer" quando avrei avuto bisogno di "Float".
Jeff Zivkovic,

Questa è semplice ma la migliore risposta! Non abbiamo davvero bisogno di patch fantasia per la classe String nella maggior parte dei casi. Questo funziona meglio per me!
Anh Nguyen,

(Float (valore) salvataggio falso)? Float (valore) .to_s == valore? Float (valore):
Numero

6

Preferisco:

config / inizializzatori / string.rb

class String
  def number?
    Integer(self).is_a?(Integer)
  rescue ArgumentError, TypeError
    false
  end
end

e poi:

[218] pry(main)> "123123123".number?
=> true
[220] pry(main)> "123 123 123".gsub(/ /, '').number?
=> true
[222] pry(main)> "123 123 123".number?
=> false

o controlla il numero di telefono:

"+34 123 456 789 2".gsub(/ /, '').number?

4

Un modo molto più semplice potrebbe essere

/(\D+)/.match('1221').nil? #=> true
/(\D+)/.match('1a221').nil? #=> false
/(\D+)/.match('01221').nil? #=> true

3
  def isint(str)
    return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/)
  end

Le risposte solo al codice non sono molto utili. Fornisci invece una spiegazione di come funziona e perché è la risposta appropriata. Vogliamo educare per il futuro in modo che la soluzione sia compresa, non risolvere la domanda immediata.
Tin Man,

3

Personalmente mi piace l'approccio basato sull'eccezione, anche se lo renderei un po 'più conciso:

class String
  def integer?(str)
    !!Integer(str) rescue false
  end
end

Tuttavia, come altri hanno già affermato, questo non funziona con le stringhe ottali.


2

Ruby 2.4 ha Regexp#match?: (con a ?)

def integer?(str)
  /\A[+-]?\d+\z/.match? str
end

Per le versioni precedenti di Ruby, c'è Regexp#===. E sebbene l'uso diretto dell'operatore di uguaglianza di casi debba essere generalmente evitato, qui sembra molto pulito:

def integer?(str)
  /\A[+-]?\d+\z/ === str
end

integer? "123"    # true
integer? "-123"   # true
integer? "+123"   # true

integer? "a123"   # false
integer? "123b"   # false
integer? "1\n2"   # false

2

Questo potrebbe non essere adatto a tutti i casi usando semplicemente:

"12".to_i   => 12
"blah".to_i => 0

potrebbe anche fare per alcuni.

Se è un numero e non 0, verrà restituito un numero. Se restituisce 0 è una stringa o 0.


11
Funziona ma non è raccomandato, da allora "12blah".to_i => 12. Ciò potrebbe causare alcuni problemi in scenari strani.
rfsbraz,

2

Ecco la mia soluzione:

# /initializers/string.rb
class String
  IntegerRegex = /^(\d)+$/

  def integer?
    !!self.match(IntegerRegex)
  end
end

# any_model_or_controller.rb
'12345'.integer? # true
'asd34'.integer? # false

Ed ecco come funziona:

  • /^(\d)+$/è un'espressione regex per trovare cifre in qualsiasi stringa. Puoi testare le tue espressioni regex e i risultati su http://rubular.com/ .
  • Lo salviamo in una costante IntegerRegexper evitare allocazioni di memoria non necessarie ogni volta che lo usiamo nel metodo.
  • integer?è un metodo interrogativo che dovrebbe tornare trueo false.
  • matchè un metodo su stringa che corrisponde alle occorrenze secondo l'espressione regex specificata nell'argomento e restituisce i valori corrispondenti o nil.
  • !!converte il risultato del matchmetodo in booleano equivalente.
  • E dichiarare il metodo nella Stringclasse esistente è il patching delle scimmie, che non cambia nulla nelle funzionalità String esistenti, ma aggiunge semplicemente un altro metodo chiamato integer?su qualsiasi oggetto String.

1
Potresti aggiungere una piccola spiegazione a questo per favore?
stef

@stef - Ho fatto lo stesso. Per favore fatemi sapere se avete ancora qualche domanda.
Sachin,

1

Espandendo la risposta di @ rado sopra si potrebbe anche usare un'istruzione ternaria per forzare il ritorno di booleani veri o falsi senza l'uso del doppio botto. Certo, la versione con doppia negazione logica è più concisa, ma probabilmente più difficile da leggere per i nuovi arrivati ​​(come me).

class String
  def is_i?
     self =~ /\A[-+]?[0-9]+\z/ ? true : false
  end
end

Considera che l'uso delle espressioni regolari costringe Ruby a fare molto più lavoro, quindi se questo viene usato in un ciclo rallenterà il codice. Ancorare l'espressione aiuta, ma i regex sono ancora significativamente più lenti.
Tin Man,

1

Per casi più generalizzati (inclusi numeri con punto decimale), puoi provare il seguente metodo:

def number?(obj)
  obj = obj.to_s unless obj.is_a? String
  /\A[+-]?\d+(\.[\d]+)?\z/.match(obj)
end

Puoi testare questo metodo in una sessione irb:

(irb)
>> number?(7)
=> #<MatchData "7" 1:nil>
>> !!number?(7)
=> true
>> number?(-Math::PI)
=> #<MatchData "-3.141592653589793" 1:".141592653589793">
>> !!number?(-Math::PI)
=> true
>> number?('hello world')
=> nil
>> !!number?('hello world')
=> false

Per una spiegazione dettagliata del regex coinvolto qui, consulta questo articolo del blog :)


Non è necessario chiamare obj.is_a? Stringperché String # to_s restituirà se stesso, il che immagino non richieda troppe elaborazioni rispetto alla .is_a?chiamata. In questo modo, effettuerai solo una chiamata in questa linea anziché una o due. Inoltre, è possibile includere direttamente !!all'interno del number?metodo, poiché, per convenzione, un nome di metodo che termina ?dovrebbe restituire un valore booleano. Saluti!
Giovanni Benussi,

-1

Mi piace il seguente, semplice:

def is_integer?(str)
  str.to_i != 0 || str == '0' || str == '-0'
end

is_integer?('123')
=> true

is_integer?('sdf')
=> false

is_integer?('-123')
=> true

is_integer?('0')
=> true

is_integer?('-0')
=> true

Attenzione però:

is_integer?('123sdfsdf')
=> true

-2

Una fodera dentro string.rb

def is_integer?; true if Integer(self) rescue false end

-3

Non sono sicuro se questo fosse in giro quando viene posta questa domanda, ma per chiunque si imbatta in questo post, il modo più semplice è:

var = "12"
var.is_a?(Integer) # returns false
var.is_a?(String) # returns true

var = 12
var.is_a?(Integer) # returns true
var.is_a?(String) # returns false

.is_a? funzionerà con qualsiasi oggetto.


Non è questa la domanda originale. OP vuole sapere se anche la stringa è un numero intero. ad es. "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == true
Marklar,
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.