Cara menguji apakah string pada dasarnya adalah bilangan bulat dalam tanda kutip menggunakan Ruby

128

Saya butuh fungsi, is_an_integer ,, di mana

  • "12".is_an_integer? mengembalikan true.
  • "blah".is_an_integer? mengembalikan salah.

Bagaimana saya bisa melakukan ini di Ruby? Saya akan menulis regex tapi saya berasumsi ada pembantu untuk ini yang saya tidak sadari.

Tony
sumber
kemungkinan duplikat Tes jika string adalah angka di Ruby on Rails
Jakob S
1
Hati-hati menggunakan solusi yang mengandalkan ekspresi reguler. Benchmark menunjukkan bahwa mereka berjalan jauh lebih lambat daripada kode biasa.
the Tin Man

Jawaban:

135

Anda dapat menggunakan ekspresi reguler. Berikut adalah fungsi dengan saran @ janm.

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

Versi yang diedit sesuai dengan komentar dari @wich:

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

Jika Anda hanya perlu memeriksa angka positif

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  
Rado
sumber
4
Tidak buruk. Di Ruby Anda biasanya menghilangkan kata kunci "kembali" jika nilai kembali dihasilkan dalam ekspresi terakhir dalam fungsi. Ini juga akan mengembalikan nilai integer nol, Anda mungkin ingin boolean, jadi sesuatu seperti !! (str = ~ / ^ [- +]? [0-9] + $ /) akan melakukan itu. Kemudian Anda bisa menambahkannya ke String dan meninggalkan argumen, menggunakan "diri" bukannya "str", dan kemudian Anda bisa mengubah nama menjadi "is_i?" ...
Januari
2
Terima kasih! Saya sama sekali tidak tahu tentang konvensi dan praktik ruby. Saya baru saja melakukan google cepat pada ruby ​​dan ekspresi reguler untuk melihat sintaks, mengubah regex untuk diterapkan pada masalah yang dihadapi, dan mengujinya. Sebenarnya cukup rapi .. Saya mungkin harus melihatnya lebih lama ketika saya memiliki lebih banyak waktu luang.
Rado
Anda punya ide yang tepat, tetapi tidak cocok dengan biner atau hex literal (lihat solusi saya yang diedit di bawah).
Sarah Mei
16
Dua komentar Anda bisa menggunakan /regexp/ === selfalih-alih !!(self =~ /regexp/)konstruk. Anda dapat menggunakan kelas karakter '\ d' alih-alih[0-9]
yang
1
Regex paling sederhana untuk integer mungkin adalah / ^ \ d + $ /
keithxm23
169

Nah, inilah cara mudah:

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

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

Saya tidak setuju dengan solusi yang memancing pengecualian untuk mengubah string - pengecualian tidak mengontrol aliran, dan Anda mungkin melakukannya dengan cara yang benar. Yang mengatakan, solusi saya di atas tidak berurusan dengan non-basis-10 integer. Jadi, inilah cara untuk dilakukan tanpa menggunakan pengecualian:

  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
Sarah Mei
sumber
27
"01" .to_i.to_s! = "01"
sepp2k
2
Tidak bisakah Anda ganti self.to_i.to_s == selfdengan Integer self rescue false?
Meredith L. Patterson
5
Anda bisa, tetapi itu akan menjadi bentuk yang buruk. Anda tidak menggunakan pengecualian sebagai aliran kontrol, dan tidak ada kode yang harus mengandung "rescue false" (atau "rescue true"). Beberapa gsub'ing sederhana akan membuat solusi saya bekerja untuk kasus tepi yang tidak ditentukan oleh OP.
Sarah Mei
4
Saya tahu banyak orang menggunakannya, dan tentu saja menyenangkan secara estetika. Bagi saya itu indikasi bahwa kode perlu direstrukturisasi. Jika Anda mengharapkan pengecualian ... itu bukan pengecualian.
Sarah Mei
2
Saya setuju bahwa pengecualian tidak boleh digunakan sebagai aliran kontrol. Saya tidak berpikir bahwa persyaratannya adalah nomor yang berorientasi pengembang harus diakui. Dalam situasi non-programmer yang dapat dilihat sebagai bug, terutama mengingat kemungkinan kebingungan di sekitar angka nol dan oktal. Juga tidak konsisten dengan to_i. Kode Anda tidak menangani case "-0123". Setelah Anda menangani kasus itu, Anda tidak perlu regexp terpisah untuk oktal. Anda bisa lebih jauh dengan menggunakan "any?". Satu-satunya pernyataan dalam fungsi Anda adalah "[/ re1 /, / re2 /, / re3 /] .any? {| Re | self = ~ re}", tanpa klausa atau pengembalian.
Januari
67

Anda dapat menggunakan Integer(str)dan melihat apakah itu memunculkan:

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

Harus ditunjukkan bahwa walaupun ini mengembalikan true untuk "01", itu tidak untuk "09", hanya karena 09tidak akan menjadi integer literal yang valid. Jika itu bukan perilaku yang Anda inginkan, Anda dapat menambahkan 10sebagai argumen kedua Integer, sehingga angka selalu ditafsirkan sebagai basis 10.

sepp2k
sumber
39
Bung ... memprovokasi pengecualian hanya untuk mengonversi angka? Pengecualian bukanlah aliran kontrol.
Sarah Mei
29
Mereka tidak, tapi sayangnya ini adalah cara kanonik untuk menentukan "integerness" dari sebuah string di Ruby. Metode menggunakan #to_iterlalu rusak karena permisif.
Avdi
17
Bagi mereka yang bertanya-tanya mengapa, Integer ("09") tidak valid karena "0" membuatnya oktal, dan 9 bukan angka oktal yang valid. osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Andrew Grimm
20
Sarah: Anda dapat menggunakan Regex tetapi untuk menangani semua kasus yang dilakukan Ruby saat mem-parsing bilangan bulat (angka negatif, hex, oktal, garis bawah misalnya 1_000_000) itu akan menjadi Regex yang sangat besar dan mudah salah. Integer()adalah kanonik karena dengan Integer ()Anda tahu pasti bahwa apa pun yang dianggap Ruby sebagai integer literal akan diterima, dan yang lainnya akan ditolak. Menggandakan apa yang bahasa sudah berikan kepada Anda bisa dibilang bau kode yang lebih buruk daripada menggunakan pengecualian untuk kontrol.
Avdi
9
@Ado Jadi sedang menciptakan kembali roda.
sepp2k
24

Anda dapat melakukan satu liner:

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

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

atau bahkan

int = Integer(str) rescue false

Bergantung pada apa yang Anda coba lakukan, Anda juga dapat langsung menggunakan blok awal dengan klausa penyelamatan:

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

  i.times do |j|
    puts j
  end
rescue ArgumentError
  puts "Not an int, doing something else"
end
Robert Klemme
sumber
1
Berkenaan dengan topik "pengecualian sebagai aliran kontrol": karena kita tidak tahu bagaimana metode yang akan digunakan kita tidak bisa benar-benar menilai apakah pengecualian cocok atau tidak. Jika string adalah input dan harus bilangan bulat, maka memberikan bilangan bulat akan membutuhkan pengecualian. Meskipun kemudian mungkin penanganannya tidak dalam metode yang sama dan kami mungkin hanya akan melakukan Integer (str) .times {| i | menempatkan saya} atau apa pun.
Robert Klemme
24
"12".match(/^(\d)+$/)      # true
"1.2".match(/^(\d)+$/)     # false
"dfs2".match(/^(\d)+$/)    # false
"13422".match(/^(\d)+$/)   # true
Maciej Krasowski
sumber
4
Itu tidak kembali truedan falsetetapi MatchDatacontoh dannil
Stefan
Ini bukan apa yang dikembalikan, tetapi jika cocok
Maciej Krasowski
5
Bungkus dengan !!atau gunakan present?jika Anda memerlukan boolean !!( "12".match /^(\d)+$/ )atau "12".match(/^(\d)+$/).present?(yang terakhir membutuhkan Rails / activesupport)
mahemoff
1
Ekspresi reguler ini tidak memperhitungkan akun: angka negatif juga angka integer yang valid . Anda sekarang menguji untuk bilangan asli yang valid atau nol.
Jochem Schulenklopper
13

Ruby 2.6.0 memungkinkan casting ke integer tanpa memunculkan exception , dan akan kembali niljika cast gagal. Dan karena nilsebagian besar berperilaku seperti falsedi Ruby, Anda dapat dengan mudah memeriksa bilangan bulat seperti:

if Integer(my_var, exception: false)
  # do something if my_var can be cast to an integer
end
Ketepatan waktu
sumber
8
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. Itu tidak diawali dengan is_. Saya menemukan bahwa konyol pada metode tanda tanya, saya suka "04".integer?jauh lebih baik daripada "foo".is_integer?.
  2. Ia menggunakan solusi yang masuk akal oleh sepp2k, yang berlaku untuk "01" dan semacamnya.
  3. Berorientasi objek, ya.
Lilleaas Agustus
sumber
1
+1 untuk penamaan #integer ?, -1 untuk mengacaukan String dengan itu :-P
Avdi
1
Ke mana lagi akan pergi? integer?("a string")ftl.
Agustus Lilleaas
2
String#integer?adalah jenis tambalan umum yang suka ditambahkan oleh setiap pembuat kode Ruby dan sepupu mereka ke bahasa tersebut, yang mengarah ke basis kode dengan tiga implementasi berbeda yang tidak kompatibel secara halus dan kerusakan yang tidak terduga. Saya belajar ini dengan cara yang sulit di proyek-proyek besar Ruby.
Avdi
Komentar yang sama seperti di atas: pengecualian tidak boleh digunakan untuk aliran kontrol.
Sarah Mei
Kelemahan: solusi ini membuang satu konversi.
Robert Klemme
7

Cara Terbaik dan Sederhana menggunakan 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

Integerjuga baik tetapi akan kembali 0untukInteger nil

Siva
sumber
Saya senang saya memperhatikan jawaban Anda. Kalau tidak, saya akan pergi dengan "Integer" ketika saya membutuhkan "Float".
Jeff Zivkovic
Ini sederhana tetapi jawaban terbaik! Kami tidak benar-benar membutuhkan patch mewah untuk kelas String di sebagian besar kasus. Ini yang terbaik untuk saya!
Anh Nguyen
(Float (nilai) penyelamatan salah)? Float (value) .to_s == value? Float (value): Integer (value): value
okliv
6

Saya lebih memilih:

config / initializers / string.rb

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

lalu:

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

atau periksa nomor telepon:

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

Cara yang lebih sederhana bisa

/(\D+)/.match('1221').nil? #=> true
/(\D+)/.match('1a221').nil? #=> false
/(\D+)/.match('01221').nil? #=> true
gouravtiwari21
sumber
3
  def isint(str)
    return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/)
  end
Amal Kumar S
sumber
Jawaban hanya kode tidak terlalu berguna. Alih-alih berikan penjelasan tentang cara kerjanya dan mengapa itu jawaban yang tepat. Kami ingin mendidik untuk masa depan sehingga solusinya dipahami, bukan memecahkan pertanyaan langsung.
the Tin Man
3

Secara pribadi saya suka pendekatan pengecualian meskipun saya akan membuatnya sedikit lebih singkat:

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

Namun, seperti yang telah dinyatakan orang lain, ini tidak berfungsi dengan string Oktal.

Eightbitraptor
sumber
2

Ruby 2.4 memiliki Regexp#match?: (dengan a ?)

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

Untuk versi Ruby yang lebih lama, ada Regexp#===. Dan meskipun penggunaan langsung dari operator kasus kesetaraan umumnya harus dihindari, itu terlihat sangat bersih di sini:

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
Stefan
sumber
2

Ini mungkin tidak cocok untuk semua kasus yang menggunakan:

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

mungkin juga dilakukan untuk beberapa.

Jika itu angka dan bukan 0 maka akan mengembalikan angka. Jika mengembalikan 0 itu adalah string atau 0.

tiga
sumber
11
Berhasil tetapi tidak direkomendasikan, karena "12blah".to_i => 12. Ini mungkin menyebabkan beberapa masalah dalam skenario aneh.
rfsbraz
2

Inilah solusi saya:

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

Dan inilah cara kerjanya:

  • /^(\d)+$/adalah ekspresi regex untuk menemukan digit dalam string apa pun. Anda dapat menguji ekspresi dan hasil regex Anda di http://rubular.com/ .
  • Kami menyimpannya dalam konstanta IntegerRegexuntuk menghindari alokasi memori yang tidak perlu setiap kali kami menggunakannya dalam metode.
  • integer? adalah metode interogatif yang harus dikembalikan true atau false.
  • matchadalah metode pada string yang cocok dengan kejadian sesuai ekspresi regex yang diberikan dalam argumen dan mengembalikan nilai yang cocok atau nil.
  • !! mengkonversi hasil match metode menjadi boolean yang setara.
  • Dan mendeklarasikan metode di Stringkelas yang ada adalah monkey patching, yang tidak mengubah apa pun dalam fungsionalitas String yang ada, tetapi hanya menambahkan metode lain yang dinamai integer?pada objek String apa pun.
Sachin
sumber
1
Bisakah Anda menambahkan sedikit penjelasan untuk ini?
stef
@stef - Saya telah melakukan hal yang sama. Harap beri tahu saya jika Anda masih memiliki pertanyaan.
Sachin
1

Memperluas jawaban @ rado di atas satu juga bisa menggunakan pernyataan ternary untuk memaksa kembalinya boolean benar atau salah tanpa menggunakan poni ganda. Memang, versi negasi logis ganda lebih singkat, tetapi mungkin lebih sulit dibaca untuk pendatang baru (seperti saya).

class String
  def is_i?
     self =~ /\A[-+]?[0-9]+\z/ ? true : false
  end
end
adeluccar
sumber
Pertimbangkan bahwa menggunakan ekspresi reguler memaksa Ruby untuk melakukan lebih banyak pekerjaan, jadi jika ini digunakan dalam satu lingkaran, itu akan memperlambat kode. Menambatkan ekspresi membantu, tetapi regex masih jauh lebih lambat.
the Tin Man
1

Untuk kasus yang lebih umum (termasuk angka dengan titik desimal), Anda dapat mencoba metode berikut:

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

Anda dapat menguji metode ini dalam sesi 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

Untuk penjelasan rinci tentang regex yang terlibat di sini, lihat artikel blog ini :)

Taiga
sumber
Tidak perlu menelepon obj.is_a? Stringkarena String # to_s akan kembali dengan sendirinya, yang saya kira tidak memerlukan terlalu banyak pemrosesan dibandingkan dengan .is_a?panggilan. Dengan cara ini, Anda hanya akan melakukan satu panggilan di baris ini alih-alih satu atau dua. Juga, Anda bisa memasukkan langsung !!ke dalam number?metode, karena dengan konvensi, nama metode yang diakhiri dengan ?seharusnya mengembalikan nilai boolean. Salam!
Giovanni Benussi
-1

Saya suka yang berikut, lurus ke depan:

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

Hati-hati:

is_integer?('123sdfsdf')
=> true
schmijos
sumber
-2

Satu liner masuk string.rb

def is_integer?; true if Integer(self) rescue false end
cb24
sumber
-3

Saya tidak yakin apakah ini ada ketika pertanyaan ini diajukan tetapi bagi siapa pun yang menemukan tulisan ini, cara paling sederhana adalah:

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? akan bekerja dengan objek apa pun.

Pemula
sumber
Bukan itu pertanyaan awal. OP ingin tahu apakah string tersebut juga bilangan bulat. mis "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == true
Marklar