Как проверить, является ли строка целым числом в кавычках с помощью Ruby

128

Мне нужна функция,, is_an_integerгде

  • "12".is_an_integer? возвращает истину.
  • "blah".is_an_integer? возвращает false.

Как я могу сделать это в Ruby? Я бы написал регулярное выражение, но предполагаю, что для этого есть помощник, о котором я не знаю.

Тони
источник
возможный дубликат Test if string is a number в Ruby on Rails
Якоб С.
1
Будьте осторожны, используя решения, основанные на регулярных выражениях. Тесты показывают, что они работают намного медленнее, чем обычный код.
Железный Человек

Ответы:

135

Вы можете использовать регулярные выражения. Вот функция с предложениями @janm.

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

Отредактированная версия согласно комментарию @wich:

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

Если вам нужно проверить только положительные числа

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  
Rado
источник
4
Неплохо. В Ruby вы обычно опускаете ключевое слово «return», если возвращаемое значение генерируется в последнем выражении функции. Это также вернет целочисленное значение, равное нулю, вы, вероятно, захотите логическое значение, поэтому что-то вроде !! (str = ~ / ^ [- +]? [0-9] + $ /) сделает это. Затем вы можете добавить его в String и опустить аргумент, используя «self» вместо «str», а затем вы можете изменить имя на «is_i?» ...
janm 05
2
Спасибо! Я понятия не имею о правилах и правилах использования Ruby. Я просто быстро погуглил по Ruby и регулярным выражениям, чтобы увидеть синтаксис, изменил регулярное выражение, чтобы применить к рассматриваемой проблеме, и протестировал его. На самом деле он довольно изящный ... Возможно, мне придется присмотреться к нему, когда у меня будет больше свободного времени.
Rado
У вас есть правильная идея, но она не соответствует двоичным или шестнадцатеричным литералам (см. Мое отредактированное решение ниже).
Сара Мей
16
Два комментария. Можно использовать /regexp/ === selfвместо !!(self =~ /regexp/)конструкции. Вы можете использовать класс символов «\ d» вместо[0-9]
которым
1
Самым простым регулярным выражением для целого числа, вероятно, является / ^ \ d + $ /
keithxm23
169

Что ж, вот простой способ:

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

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

Я не согласен с решениями, которые вызывают исключение для преобразования строки - исключения не являются потоком управления, и вы также можете сделать это правильно. Тем не менее, мое вышеприведенное решение не касается целых чисел, отличных от десятичной. Итак, вот способ обойтись, не прибегая к исключениям:

  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 05
2
Не могли бы вы заменить self.to_i.to_s == selfна Integer self rescue false?
Мередит Л. Паттерсон
5
Можно, но это было бы дурным тоном. Вы не используете исключения в качестве потока управления, и ни один код никогда не должен содержать «rescue false» (или «rescue true»). Некоторый простой gsub'ing заставил бы мое решение работать для крайних случаев, не указанных OP.
Сара Мей
4
Я знаю, что многие люди его используют, и это, безусловно, эстетично. Для меня это признак того, что код нуждается в реструктуризации. Если вы ожидаете исключения ... это не исключение.
Сара Мей
2
Я согласен с тем, что исключения не следует использовать в качестве потока управления. Я не думаю, что требуется признание номеров, ориентированных на разработчиков. В ситуациях, не связанных с программированием, это можно рассматривать как ошибку, особенно с учетом возможной путаницы с начальными нулями и восьмеричными числами. Также не соответствует to_i. Ваш код не обрабатывает случай «-0123». Как только вы справитесь с этим случаем, вам не понадобится отдельное регулярное выражение для восьмеричного. Вы можете просто продолжить, используя «любой?». Единственным оператором в вашей функции может быть "[/ re1 /, / re2 /, / re3 /] .any? {| Re | self = ~ re}", без предложений if или возвратов.
janm 06
67

Вы можете использовать Integer(str)и посмотреть, поднимается ли он:

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

Следует отметить, что, хотя это действительно возвращает true для "01", это не для "09", просто потому, 09что не будет действительным целочисленным литералом. Если это не то поведение, которое вам нужно, вы можете добавить его 10в качестве второго аргумента Integer, чтобы число всегда интерпретировалось как основание 10.

sepp2k
источник
39
Чувак ... провоцируешь исключение только для преобразования числа? Исключения - это не поток управления.
Сара Мей
29
Это не так, но, к сожалению, это канонический способ определения «целочисленности» строки в Ruby. Методы использования #to_iслишком ломаются из-за своей вседозволенности.
Avdi
17
Для тех, кто задается вопросом, почему, Целое число («09») недействительно, потому что «0» делает его восьмеричным, а 9 - недопустимым восьмеричным числом. osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Эндрю Гримм,
20
Сара: вы можете использовать регулярное выражение, но для обработки всех случаев, которые делает Ruby при разборе целых чисел (отрицательные числа, шестнадцатеричные, восьмеричные, знаки подчеркивания, например, 1_000_000), это будет очень большое регулярное выражение, и легко ошибиться. Integer()является каноническим, потому что Integer ()вы точно знаете, что все, что Ruby считает целочисленным литералом, будет принято, а все остальное будет отклонено. Дублирование того, что уже дает вам язык, - возможно, худший запах кода, чем использование исключений для контроля.
Avdi
9
@Rado So изобретает велосипед.
sepp2k
24

Вы можете сделать один лайнер:

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

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

или даже

int = Integer(str) rescue false

В зависимости от того, что вы пытаетесь сделать, вы также можете напрямую использовать начальный конечный блок с предложением rescue:

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

  i.times do |j|
    puts j
  end
rescue ArgumentError
  puts "Not an int, doing something else"
end
Роберт Клемме
источник
1
Что касается темы «исключение как поток управления»: поскольку мы не знаем, как использовать рассматриваемый метод, мы не можем судить, подходят ли исключения или нет. Если строка вводится и должна быть целым числом, то предоставление нецелого числа потребует исключения. Хотя тогда, возможно, обработка не в том же методе, и мы, вероятно, просто будем использовать Integer (str) .times {| i | помещает i} или что-то еще.
Роберт Клемме,
24
"12".match(/^(\d)+$/)      # true
"1.2".match(/^(\d)+$/)     # false
"dfs2".match(/^(\d)+$/)    # false
"13422".match(/^(\d)+$/)   # true
Мацей Красовски
источник
4
Он не возвращается, trueа falseтолько MatchDataэкземпляры иnil
Стефан
Это не то, что он возвращает, но если оно совпадает,
Мацей Красовски
5
Оберните его !!или используйте, present?если вам нужно логическое значение !!( "12".match /^(\d)+$/ )или "12".match(/^(\d)+$/).present?(последнее требует Rails / activesupport)
mahemoff
1
Это регулярное выражение не принимает во внимание знак: отрицательные числа также являются действительными целыми числами . Теперь вы проверяете действительные натуральные числа или ноль.
Йохем Шуленклоппер,
13

Ruby 2.6.0 разрешает приведение к целому числу без возникновения исключения и вернется, nilесли приведение завершится неудачно. И поскольку в nilосновном ведет себя как falseв Ruby, вы можете легко проверить целое число следующим образом:

if Integer(my_var, exception: false)
  # do something if my_var can be cast to an integer
end
Timitry
источник
8
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. Он не имеет префикса is_. Я нахожу это глупым в методах вопросительного знака, мне нравится "04".integer?намного больше, чем "foo".is_integer?.
  2. Он использует разумное решение от sepp2k, которое проходит "01"и тому подобное.
  3. Объектно-ориентированный, ура.
Август Лиллеас
источник
1
+1 за имя #integer ?, -1 за загромождение String им :-P
Avdi
1
Куда еще оно могло пойти? integer?("a string")FTL.
Август Лиллеа, 05
2
String#integer?это своего рода общий патч, который каждый Ruby-программист и его двоюродный брат любят добавлять в язык, что приводит к кодовым базам с тремя различными слегка несовместимыми реализациями и неожиданным сбоям. Я усвоил это на собственном горьком опыте на больших проектах Ruby.
Avdi
Тот же комментарий, что и выше: исключения не должны использоваться для потока управления.
Сара Мей
Оборотная сторона: это решение приводит к потере одной конверсии.
Роберт Клемме
7

Самый лучший и простой способ - использовать 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также хорошо , но он вернется 0кInteger nil

Сива
источник
Я рад, что заметил твой ответ. В противном случае я бы выбрал «Integer», когда мне нужен «Float».
Джефф Живкович
Это простой, но лучший ответ! В большинстве случаев нам действительно не нужны причудливые патчи для класса String. Это лучше всего работает для меня!
Anh Nguyen
(Float (значение) rescue false)? Float (значение) .to_s == значение? Float (значение): Integer (значение): value
okliv
6

Я предпочитаю:

конфиг / Инициализаторы / string.rb

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

а потом:

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

или проверьте номер телефона:

"+34 123 456 789 2".gsub(/ /, '').number?
skozz
источник
4

Более простой способ мог бы быть

/(\D+)/.match('1221').nil? #=> true
/(\D+)/.match('1a221').nil? #=> false
/(\D+)/.match('01221').nil? #=> true
gouravtiwari21
источник
3
  def isint(str)
    return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/)
  end
Амаль Кумар С
источник
Ответы только на код не очень полезны. Вместо этого объясните, как это работает и почему это правильный ответ. Мы хотим просвещать нас в будущем, чтобы решение было понятным, а не непосредственный вопрос.
Железный Человек
3

Лично мне нравится подход с исключениями, хотя я бы сделал его немного короче:

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

Однако, как уже говорили другие, это не работает с восьмеричными строками.

eightbitraptor
источник
2

Ruby 2.4 имеет Regexp#match?: (с ?)

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

Для более старых версий Ruby существует Regexp#===. И хотя прямого использования оператора равенства case следует избегать, здесь он выглядит очень чисто:

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

Это может не подходить для всех случаев, просто используя:

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

может также подойти для некоторых.

Если это число, а не 0, он вернет число. Если он возвращает 0, это либо строка, либо 0.

три
источник
11
Работает , но это не рекомендуется, так как "12blah".to_i => 12. Это может вызвать некоторые проблемы в необычных сценариях.
rfsbraz
2

Вот мое решение:

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

А вот как это работает:

  • /^(\d)+$/является регулярным выражением выражения для поиска цифр в любой строке. Вы можете проверить свои регулярные выражения и результаты на http://rubular.com/ .
  • Мы сохраняем его в константе, IntegerRegexчтобы избежать ненужного выделения памяти каждый раз, когда мы используем его в методе.
  • integer?- это вопросительный метод, который должен возвращать trueили false.
  • match - это метод для строки, который сопоставляет вхождения в соответствии с заданным выражением регулярного выражения в аргументе и возвращает сопоставленные значения или nil .
  • !!преобразует результат matchметода в эквивалентное логическое значение.
  • А объявление метода в существующем Stringклассе - это исправление обезьяны, которое ничего не меняет в существующих функциях String, а просто добавляет другой метод, названный integer?для любого объекта String.
Сэчин
источник
1
Не могли бы вы добавить к этому небольшое пояснение?
Стеф
@stef - я сделал то же самое. Пожалуйста, дайте мне знать, если у вас все еще есть вопросы.
Sachin
1

Расширяя ответ @rado выше, можно также использовать тернарный оператор для принудительного возврата истинных или ложных логических значений без использования двойного удара. Конечно, версия с двойным логическим отрицанием более лаконична, но, вероятно, труднее читать новичкам (например, мне).

class String
  def is_i?
     self =~ /\A[-+]?[0-9]+\z/ ? true : false
  end
end
adeluccar
источник
Учтите, что использование регулярных выражений заставляет Ruby выполнять гораздо больше работы, поэтому, если это используется в цикле, это замедлит код. Привязка выражения помогает, но регулярное выражение все еще значительно медленнее.
Железный Человек
1

Для более общих случаев (включая числа с десятичной точкой) вы можете попробовать следующий метод:

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

Вы можете протестировать этот метод в сеансе 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

Подробное объяснение задействованного здесь регулярного выражения можно найти в этой статье блога :)

тайга
источник
Вызывать не обязательно, obj.is_a? Stringпотому что String # to_s вернет сам себя, что, я думаю, не требует слишком большой обработки по сравнению с .is_a?вызовом. Таким образом, вы будете делать только один звонок в этой линии вместо одного или двух. Кроме того, вы можете включить его непосредственно !!в number?метод, поскольку по соглашению имя метода, которое заканчивается на ?, должно возвращать логическое значение. С уважением!
Джованни Бенусси
-1

Мне нравится следующее:

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

Но осторожно:

is_integer?('123sdfsdf')
=> true
schmijos
источник
-2

Один лайнер в string.rb

def is_integer?; true if Integer(self) rescue false end
CB24
источник
-3

Я не уверен, было ли это когда-то, когда задают этот вопрос, но для всех, кто наткнется на этот пост, самый простой способ:

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? будет работать с любым объектом.

Новичок
источник
Это не то, что задает исходный вопрос. OP также хочет знать, является ли строка целым числом. например "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == true
Marklar