Como testar se uma string é basicamente um número inteiro entre aspas usando Ruby


128

Eu preciso de uma função is_an_integer, onde

  • "12".is_an_integer? retorna verdadeiro.
  • "blah".is_an_integer? retorna falso.

Como posso fazer isso em Ruby? Eu escreveria uma regex, mas estou assumindo que existe um ajudante para isso que não conheço.



1
Tenha cuidado ao usar soluções baseadas em expressões regulares. Os benchmarks mostram que eles correm muito mais lentamente que o código normal.
the Tin Man

Respostas:


135

Você pode usar expressões regulares. Aqui está a função com as sugestões de @ janm.

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

Uma versão editada de acordo com o comentário de @wich:

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

Caso você precise apenas verificar números positivos

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

4
Não é ruim. Em Ruby, você geralmente omite a palavra-chave "return" se o valor de retorno for gerado na última expressão na função. Isso também retornará um valor inteiro zero, você provavelmente desejará um booleano, então algo como !! (str = ~ / ^ [- +]? [0-9] + $ /) faria isso. Em seguida, você pode adicioná-lo a String e deixar de fora o argumento, usando "self" em vez de "str" ​​e alterando o nome para "is_i?" ...
JANM

2
Obrigado! Não tenho absolutamente nenhuma pista sobre convenções e práticas de rubi. Acabei de fazer um rápido google no ruby ​​e expressões regulares para ver a sintaxe, alterei o regex para aplicar ao problema em questão e o testei. Na verdade, é bem legal. Talvez eu precise dar uma olhada mais longa quando tiver mais tempo livre.
Rado

Você tem a idéia certa, mas ela não corresponde aos literais binários ou hexadecimais (veja minha solução editada abaixo).
242 Sarah Mei

16
Dois comentários Você pode usar em /regexp/ === selfvez da !!(self =~ /regexp/)construção. Você pode usar a classe de caracteres '\ d' em vez de[0-9]
que

1
A regex simples para um inteiro provavelmente é / ^ \ d + $ /
keithxm23

169

Bem, aqui está o caminho mais fácil:

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

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

Não concordo com as soluções que provocam uma exceção para converter a string - as exceções não são o fluxo de controle e você pode fazê-lo da maneira certa. Dito isto, minha solução acima não lida com números inteiros que não sejam da base 10. Então, aqui está o caminho a seguir sem recorrer a exceções:

  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 5/08/09

2
Você não poderia substituir self.to_i.to_s == selfcom Integer self rescue false?
Meredith L. Patterson

5
Você poderia, mas isso seria uma má forma. Você não usa exceções como fluxo de controle, e o código de ninguém deve conter "resgate falso" (ou "resgate verdadeiro"). Algum gsub'ing simples faria minha solução funcionar para casos extremos não especificados pelo OP.
242 Sarah Mei

4
Eu sei que muitas pessoas usam, e é certamente esteticamente agradável. Para mim, porém, é uma indicação de que o código precisa de reestruturação. Se você está esperando uma exceção ... não é uma exceção.
242 Sarah Mei

2
Concordo que exceções não devem ser usadas como fluxo de controle. Não acho que o requisito seja que os números orientados ao desenvolvedor sejam reconhecidos. Em situações não programadoras, isso pode ser visto como um bug, principalmente devido à possível confusão em torno de zeros à esquerda e octal. Também não é consistente com to_i. Seu código não lida com o caso "-0123". Depois de lidar com esse caso, você não precisa de um regexp separado para octal. Você pode simplesmente continuar usando "any?". A única instrução em sua função pode ser "[/ re1 /, / re2 /, / re3 /]. Qualquer um? {| Re | self = ~ re}", sem cláusulas ou retornos if.
JANM

67

Você pode usar Integer(str)e ver se isso gera:

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

Deve-se ressaltar que, embora isso retorne true para "01", não retorna "09", simplesmente porque 09não seria um literal inteiro válido. Se esse não é o comportamento que você deseja, você pode adicionar 10como um segundo argumento para Integerque o número seja sempre interpretado como base 10.


39
Cara ... provocando uma exceção só para converter um número? Exceções não são fluxo de controle.
Sarah Mei

29
Eles não são, mas infelizmente essa é a maneira canônica de determinar a "integridade" de uma string no Ruby. Métodos usando #to_isão muito quebrados por causa de sua permissividade.
Avdi

17
Para aqueles que se perguntam por que, o número inteiro ("09") não é válido porque o "0" o torna octal e 9 não é um número octal válido. osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Andrew Grimm

20
Sarah: você pode usar um Regex, mas para lidar com todos os casos que Ruby faz ao analisar números inteiros (números negativos, hex, octal, sublinhados, por exemplo, 1_000_000), seria um Regex muito grande e fácil de se errar. Integer()é canônico, porque com Integer ()certeza você sabe que tudo o que Ruby considera um literal inteiro será aceito e todo o resto será rejeitado. Duplicar o que a linguagem já oferece é sem dúvida um cheiro de código pior do que usar exceções para controle.
Avdi

9
@Rado Então, é reinventar a roda.
sepp2k

24

Você pode fazer um forro:

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

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

ou mesmo

int = Integer(str) rescue false

Dependendo do que você está tentando fazer, você também pode usar diretamente um bloco de término inicial com a cláusula de resgate:

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

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

1
Com relação ao tópico "exceção como fluxo de controle": como não sabemos como o método em questão deve ser usado, não podemos realmente julgar se as exceções se encaixariam ou não. Se a sequência for inserida e for necessário que seja um número inteiro, fornecer um número inteiro não justificaria uma exceção. Embora, então, talvez o tratamento não esteja no mesmo método e provavelmente faríamos apenas Integer (str) .times {| i | coloca i} ou o que quer.
926 Robert Klemme

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

4
Não volta true e false, mas MatchDatacasos enil
Stefan

Não é o que retorna, mas se corresponder #
Maciej Krasowski 8/08/14

5
Envolva-o !!ou use-o present?se precisar de um valor booleano!!( "12".match /^(\d)+$/ ) ou "12".match(/^(\d)+$/).present?(o último que requerem Rails / ActiveSupport)
mahemoff

1
Essa expressão regular não leva em consideração o sinal: números negativos também são números inteiros válidos . Agora você está testando números naturais válidos ou zero.
Jochem Schulenklopper 12/09/19


8
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. Não é prefixado com is_. Acho isso bobo em métodos de questionmark, eu gosto "04".integer?muito melhor do que "foo".is_integer?.
  2. Ele usa a solução sensível por sepp2k, que passa por "01"e tal.
  3. Orientado a objetos, yay.

1
+1 por nome #integer ?, -1 por desordenar String com ele :-P
Avdi

1
Para onde mais iria? integer?("a string")ftl.
August Lilleaas

2
String#integer?é o tipo de patch comum que todo codificador Ruby e seu primo gosta de adicionar à linguagem, levando a bases de código com três implementações sutilmente incompatíveis diferentes e quebra inesperada. Aprendi isso da maneira mais difícil em grandes projetos Ruby.
Avdi

O mesmo comentário acima: as exceções não devem ser usadas para o fluxo de controle.
Sarah Mei

Desvantagem: esta solução está desperdiçando uma conversão.
Robert Klemme 31/01

7

A maneira Melhor e Simples é usar 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

Integertambém é bom, mas retornará 0paraInteger nil


Estou feliz por ter notado sua resposta. Caso contrário, eu teria optado por "Inteiro" quando precisar de "Flutuar".
Jeff Zivkovic

Isso é simples, mas a melhor resposta! Na verdade, não precisamos de um patch sofisticado para a classe String na maioria dos casos. Isso funciona melhor para mim!
Anh Nguyen

(Recuperação flutuante (valor) falsa)? Flutuar (valor) .to_s == valor? Flutuante (valor): Inteiro (valor): valor
okliv

6

Eu prefiro:

config / inicializadores / string.rb

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

e depois:

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

ou verifique o número de telefone:

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

4

Uma maneira muito mais simples poderia ser

/(\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

As respostas apenas de código não são muito úteis. Em vez disso, forneça uma explicação de como funciona e por que é a resposta apropriada. Queremos educar para o futuro, para que a solução seja entendida, e não para resolver a questão imediata.
o homem de lata

3

Pessoalmente, gosto da abordagem de exceção, embora a torne um pouco mais concisa:

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

No entanto, como outros já declararam, isso não funciona com cadeias octais.


2

O Ruby 2.4 possui Regexp#match?: (com um ?)

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

Para versões mais antigas do Ruby, existe Regexp#===. E embora o uso direto do operador de igualdade de caso deva ser geralmente evitado, ele parece muito limpo aqui:

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

Isso pode não ser adequado para todos os casos usando simplesmente:

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

também pode fazer para alguns.

Se for um número e não 0, ele retornará um número. Se retornar 0, será uma sequência ou 0.


11
Funciona, mas não é recomendado, desde então "12blah".to_i => 12. Isso pode causar alguns problemas em cenários estranhos.
perfil completo de rfsbraz

2

Aqui está a minha solução:

# /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

E aqui está como funciona:

  • /^(\d)+$/é uma expressão de expressão regular para encontrar dígitos em qualquer sequência. Você pode testar suas expressões e resultados de expressões regulares em http://rubular.com/ .
  • Nós o salvamos de forma constante IntegerRegex para evitar alocação desnecessária de memória toda vez que o usamos no método.
  • integer?é um método interrogativo que deve retornar trueoufalse .
  • match é um método na string que corresponde às ocorrências de acordo com a expressão regex especificada no argumento e retorna os valores correspondentes ou nil .
  • !!converte o resultado do matchmétodo em booleano equivalente.
  • E declarar o método na Stringclasse existente é o patch do macaco, que não altera nada nas funcionalidades existentes da String, mas apenas adiciona outro método nomeado integer?em qualquer objeto String.

1
Você poderia adicionar uma pequena explicação para isso, por favor?
stef 24/05

@stef - Eu fiz o mesmo. Entre em contato se você ainda tiver alguma dúvida.
Sachin 25/05

1

Expandir a resposta do @ rado acima pode também usar uma declaração ternária para forçar o retorno de booleanos verdadeiros ou falsos sem o uso de double bangs. É verdade que a versão de dupla negação lógica é mais concisa, mas provavelmente mais difícil de ler para os iniciantes (como eu).

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

Considere que o uso de expressões regulares força o Ruby a fazer muito mais trabalho; portanto, se isso for usado em loop, o código ficará lento. Ancorar a expressão ajuda, mas o regex ainda é significativamente mais lento.
o homem de lata

1

Para casos mais generalizados (incluindo números com ponto decimal), você pode tentar o seguinte método:

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

Você pode testar este método em uma sessão 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

Para uma explicação detalhada do regex envolvido aqui, confira este artigo do blog :)


Não é necessário chamar obj.is_a? Stringporque a String # to_s retornará automaticamente , o que eu acho que não requer muito processamento em comparação com a .is_a?chamada. Dessa forma, você fará apenas uma chamada nesta linha em vez de uma ou duas. Além disso, você pode incluir diretamente !!dentro do number?método, porque por convenção, um nome de método que termina com ?deve retornar um valor booleano. Saudações!
Giovanni Benussi 5/10

-1

Eu gosto do seguinte, direto:

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

Cuidado, porém:

is_integer?('123sdfsdf')
=> true

-2

Um forro em string.rb

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

-3

Não tenho certeza se isso aconteceu quando essa pergunta foi feita, mas para qualquer um que se deparar com este post, a maneira mais simples é:

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? funcionará com qualquer objeto.


Não é isso que a pergunta original está fazendo. O OP quer saber se a sequência também é um número inteiro. por exemplo "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == true
Marklar 25/04
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.