Jak sprawdzić, czy łańcuch jest w zasadzie liczbą całkowitą w cudzysłowie, używając Rubiego

129

Potrzebuję funkcji is_an_integer, gdzie

  • "12".is_an_integer? zwraca prawdę.
  • "blah".is_an_integer? zwraca false.

Jak mogę to zrobić w Rubim? Napisałbym wyrażenie regularne, ale zakładam, że istnieje pomocnik, którego nie jestem świadomy.

Tony
źródło
1
Zachowaj ostrożność, używając rozwiązań opartych na wyrażeniach regularnych. Testy porównawcze pokazują, że działają one znacznie wolniej niż zwykły kod.
Tin Man

Odpowiedzi:

136

Możesz używać wyrażeń regularnych. Oto funkcja z sugestiami @ janm.

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

Wersja zredagowana zgodnie z komentarzem z @wich:

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

Jeśli potrzebujesz tylko sprawdzić liczby dodatnie

  if !/\A\d+\z/.match(string_to_check)
      #Is not a positive number
  else
      #Is all good ..continue
  end  
Rado
źródło
4
Nie jest zły. W Rubim zwykle pomija się słowo kluczowe „return”, jeśli wartość zwracana jest generowana w ostatnim wyrażeniu funkcji. To również zwróci wartość całkowitą równą zero, prawdopodobnie potrzebujesz wartości logicznej, więc coś takiego jak !! (str = ~ / ^ [- +]? [0-9] + $ /) powinno to zrobić. Następnie możesz dodać go do String i pominąć argument, używając „self” zamiast „str”, a następnie możesz zmienić nazwę na „is_i?” ...
janm
2
Dzięki! Nie mam pojęcia o konwencjach i praktykach rubinowych. Po prostu wyszukałem w Google Ruby i wyrażenia regularne, aby zobaczyć składnię, zmieniłem wyrażenie regularne tak, aby odnosiło się do problemu, i przetestowałem. Właściwie jest całkiem schludny ... Być może będę musiał spojrzeć na to dłużej, kiedy będę miał więcej wolnego czasu.
Rado
Masz dobry pomysł, ale nie pasuje on do literałów binarnych lub szesnastkowych (zobacz moje edytowane rozwiązanie poniżej).
Sarah Mei
16
Dwie uwagi. Możesz użyć /regexp/ === selfzamiast !!(self =~ /regexp/)konstrukcji. Możesz użyć klasy znaków „\ d” zamiast[0-9]
co
1
Najprostszym wyrażeniem regularnym dla liczby całkowitej jest prawdopodobnie / ^ \ d + $ /
keithxm23
173

Oto prosty sposób:

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

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

Nie zgadzam się z rozwiązaniami, które prowokują wyjątek do konwersji łańcucha - wyjątki nie są sterowaniem przepływem i równie dobrze możesz zrobić to we właściwy sposób. To powiedziawszy, moje rozwiązanie powyżej nie dotyczy liczb całkowitych innych niż dziesiętne. Oto sposób na zrobienie tego bez uciekania się do wyjątków:

  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
źródło
27
"01" .to_i.to_s! = "01"
sepp2k
2
Nie udało się zamienić self.to_i.to_s == selfz Integer self rescue false?
Meredith L. Patterson
5
Mógłbyś, ale to byłaby zła forma. Nie używasz wyjątków jako przepływu sterowania i żaden kod nie powinien nigdy zawierać wyrażenia „rescue false” (lub „rescue true”). Niektóre proste polecenia gsub sprawią, że moje rozwiązanie będzie działało w przypadkach skrajnych, które nie są określone w OP.
Sarah Mei
4
Wiem, że używa go wiele osób i na pewno jest to estetyczne. Dla mnie to jednak wskazówka, że ​​kod wymaga restrukturyzacji. Jeśli spodziewasz się wyjątku ... to nie jest wyjątek.
Sarah Mei
2
Zgadzam się, że wyjątki nie powinny być używane jako przepływ kontroli. Nie sądzę, aby wymaganiem było rozpoznawanie liczb zorientowanych na programistów. W sytuacjach nieprogramistycznych, które mogą być postrzegane jako błąd, szczególnie biorąc pod uwagę możliwe zamieszanie wokół zer wiodących i ósemkowych. Również niezgodne z to_i. Twój kod nie obsługuje przypadku „-0123”. Gdy już zajmiesz się tym przypadkiem, nie potrzebujesz osobnego wyrażenia regularnego dla ósemkowego. Możesz po prostu dalej, używając „any?”. Jedyną instrukcją w Twojej funkcji może być „[/ re1 /, / re2 /, / re3 /] .any? {| Re | self = ~ re}”, bez klauzul if lub return.
janm
67

Możesz użyć Integer(str)i zobaczyć, czy podnosi:

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

Należy zauważyć, że chociaż zwraca to prawdę dla "01", to nie dla "09", po prostu dlatego, 09że nie byłby prawidłowym literałem całkowitoliczbowym. Jeśli nie chcesz tego zachowania, możesz dodać 10jako drugi argument Integer, aby liczba była zawsze interpretowana jako podstawa 10.

sepp2k
źródło
40
Koleś ... prowokujesz wyjątek tylko po to, by przekonwertować liczbę? Wyjątki nie są przepływem kontroli.
Sarah Mei
29
Tak nie jest, ale niestety jest to kanoniczny sposób określenia „całkowitej” łańcucha w Rubim. Metody używające #to_isą po prostu zbyt zepsute ze względu na ich pobłażliwość.
Avdi,
17
Dla tych, którzy zastanawiają się dlaczego, liczba całkowita („09”) nie jest poprawna, ponieważ „0” daje liczbę ósemkową, a 9 nie jest prawidłową liczbą ósemkową. osdir.com/ml/lang.ruby.general/2002-08/msg00247.html
Andrew Grimm
20
Sarah: możesz użyć Regex, ale aby poradzić sobie ze wszystkimi przypadkami, które robi Ruby podczas analizowania liczb całkowitych (liczby ujemne, szesnastkowe, ósemkowe, podkreślenia, np. 1_000_000), byłby to bardzo duży Regex i łatwy do pomyłki. Integer()jest kanoniczne, ponieważ z Integer ()pewnością wiesz, że wszystko, co Ruby uzna za literał całkowity, zostanie zaakceptowane, a wszystko inne zostanie odrzucone. Powielanie tego, co już daje język, jest prawdopodobnie gorszym zapachem kodu niż używanie wyjątków do kontroli.
Avdi,
9
@Rado Tak więc odkrywanie koła na nowo.
sepp2k
24

Możesz zrobić jedną wkładkę:

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

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

lub nawet

int = Integer(str) rescue false

W zależności od tego, co próbujesz zrobić, możesz również bezpośrednio użyć bloku początku końca z klauzulą ​​ratunkową:

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

  i.times do |j|
    puts j
  end
rescue ArgumentError
  puts "Not an int, doing something else"
end
Robert Klemme
źródło
1
W odniesieniu do tematu „wyjątek jako przepływ kontroli”: ponieważ nie wiemy, w jaki sposób dana metoda ma być używana, nie możemy tak naprawdę ocenić, czy wyjątki będą pasować, czy nie. Jeśli łańcuch jest wejściowy i musi być liczbą całkowitą, podanie wartości innej niż całkowita gwarantowałoby wyjątek. Chociaż wtedy być może obsługa nie odbywa się w tej samej metodzie i prawdopodobnie zrobilibyśmy po prostu Integer (str) .times {| i | umieszcza i} lub cokolwiek.
Robert Klemme
24
"12".match(/^(\d)+$/)      # true
"1.2".match(/^(\d)+$/)     # false
"dfs2".match(/^(\d)+$/)    # false
"13422".match(/^(\d)+$/)   # true
Maciej Krasowski
źródło
4
To nie wraca truei falseale MatchDatainstancji inil
Stefan
To nie to, co wraca, ale jeśli pasuje
Maciej Krasowski
5
Owiń go !!lub użyj, present?jeśli potrzebujesz boolean !!( "12".match /^(\d)+$/ )lub "12".match(/^(\d)+$/).present?(ten ostatni wymaga Rails /
activesupport
1
To wyrażenie regularne nie bierze pod uwagę znaku: liczby ujemne są również prawidłowymi liczbami całkowitymi . Testujesz teraz poprawne liczby naturalne lub zero.
Jochem Schulenklopper
15

Ruby 2.6.0 umożliwia rzutowanie na liczbę całkowitą bez zgłaszania wyjątku i zwraca, niljeśli rzutowanie się nie powiedzie. A ponieważ w nilwiększości zachowuje się jak falsew Rubim, możesz łatwo sprawdzić liczbę całkowitą, taką jak ta:

if Integer(my_var, exception: false)
  # do something if my_var can be cast to an integer
end
Timitry
źródło
8
class String
  def integer?
    Integer(self)
    return true
  rescue ArgumentError
    return false
  end
end
  1. Nie ma przedrostka is_. Uważam to za głupie, jeśli chodzi o metody ze znakami zapytania, lubię "04".integer?dużo lepiej niż "foo".is_integer?.
  2. Wykorzystuje rozsądne rozwiązanie firmy sepp2k, które mija "01"i takie.
  3. Zorientowany obiektowo, tak.
August Lilleaas
źródło
1
+1 za nazwanie go #integer ?, -1 za zagracanie String with it :-P
Avdi
1
Gdzie indziej by to poszło? integer?("a string")ftl.
Sierpień Lilleaas
2
String#integer?to rodzaj powszechnej łatki, którą każdy programista Rubiego i ich kuzyn lubi dodawać do języka, co prowadzi do baz kodu z trzema różnymi, nieznacznie niekompatybilnymi implementacjami i nieoczekiwanymi awariami. Nauczyłem się tego na własnej skórze w dużych projektach Ruby.
Avdi,
Taki sam komentarz jak powyżej: wyjątki nie powinny być używane do sterowania przepływem.
Sarah Mei
Wada: to rozwiązanie marnuje jedną konwersję.
Robert Klemme
7

Najlepszym i prostym sposobem jest użycie 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

Integerjest również dobre, ale powróci 0doInteger nil

Siva
źródło
Cieszę się, że zauważyłem twoją odpowiedź. W przeciwnym razie wybrałbym „Integer”, gdy potrzebowałbym „Float”.
Jeff Zivkovic
To proste, ale najlepsza odpowiedź! W większości przypadków nie potrzebujemy wyszukanej łatki do klasy String. To działa najlepiej dla mnie!
Anh Nguyen
(Float (wartość) rescue false)? Float (wartość) .to_s == wartość? Float (wartość): Integer (wartość): wartość
okliv
6

Wolę:

config / initializers / string.rb

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

i wtedy:

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

lub sprawdź numer telefonu:

"+34 123 456 789 2".gsub(/ /, '').number?
skozz
źródło
4

Mógłby być o wiele prostszy sposób

/(\D+)/.match('1221').nil? #=> true
/(\D+)/.match('1a221').nil? #=> false
/(\D+)/.match('01221').nil? #=> true
gouravtiwari21
źródło
3
  def isint(str)
    return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/)
  end
Amal Kumar S.
źródło
Tylko kody odpowiedzi nie są zbyt przydatne. Zamiast tego wyjaśnij, jak to działa i dlaczego jest to właściwa odpowiedź. Chcemy uczyć na przyszłość, aby zrozumieć rozwiązanie, a nie rozwiązać bezpośrednie pytanie.
Tin Man,
3

Osobiście podoba mi się podejście wyjątkowe, chociaż chciałbym, aby było trochę bardziej zwięzłe:

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

Jednak, jak już powiedzieli inni, nie działa to z ciągami ósemkowymi.

osiembitraptor
źródło
2

Ruby 2.4 ma Regexp#match?: (z ?)

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

W przypadku starszych wersji Ruby jest Regexp#===. I chociaż generalnie należy unikać bezpośredniego użycia operatora równości wielkości liter, wygląda to bardzo przejrzyście:

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
źródło
2

Może to nie być odpowiednie dla wszystkich przypadków, po prostu używając:

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

może też zrobić dla niektórych.

Jeśli jest to liczba, a nie 0, zwróci liczbę. Jeśli zwraca 0, jest to ciąg znaków lub 0.

trzy
źródło
11
Działa, ale nie jest zalecane, ponieważ "12blah".to_i => 12. Może to powodować problemy w dziwnych scenariuszach.
rfsbraz
2

Oto moje rozwiązanie:

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

A oto jak to działa:

  • /^(\d)+$/jest wyrażeniem regularnym służącym do znajdowania cyfr w dowolnym ciągu. Możesz przetestować swoje wyrażenia regularne i wyniki pod adresem http://rubular.com/ .
  • Zapisujemy go w stałej, IntegerRegexaby uniknąć niepotrzebnego przydziału pamięci za każdym razem, gdy używamy go w metodzie.
  • integer?jest metodą pytającą, która powinna zwrócić truelub false.
  • matchjest metodą na łańcuchu, która dopasowuje wystąpienia zgodnie z podanym wyrażeniem regularnym w argumencie i zwraca dopasowane wartości lub nil.
  • !!konwertuje wynik matchmetody na równoważną wartość logiczną.
  • A zadeklarowanie metody w istniejącej Stringklasie jest małpą poprawką, która nie zmienia niczego w istniejących funkcjach String, ale po prostu dodaje inną metodę nazwaną integer?na dowolnym obiekcie String.
Sachin
źródło
1
Czy mógłbyś dodać do tego małe wyjaśnienie?
stef
@stef - zrobiłem to samo. Daj mi znać, jeśli nadal masz jakieś pytania.
Sachin
1

Rozszerzając powyższą odpowiedź @ rado, można by również użyć trójskładnikowego stwierdzenia, aby wymusić powrót prawdziwych lub fałszywych wartości logicznych bez użycia podwójnej grzywki. To prawda, wersja z podwójną logiczną negacją jest bardziej zwięzła, ale prawdopodobnie trudniejsza do odczytania dla nowoprzybyłych (takich jak ja).

class String
  def is_i?
     self =~ /\A[-+]?[0-9]+\z/ ? true : false
  end
end
adeluccar
źródło
Weź pod uwagę, że użycie wyrażeń regularnych zmusza Rubiego do wykonania o wiele więcej pracy, więc jeśli zostanie użyte w pętli, spowolni to kod. Zakotwiczenie wyrażenia pomaga, ale wyrażenia regularne są nadal znacznie wolniejsze.
Tin Man
1

W przypadku bardziej uogólnionych przypadków (w tym liczb z kropką dziesiętną) możesz wypróbować następującą metodę:

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

Możesz przetestować tę metodę w sesji 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

Aby uzyskać szczegółowe wyjaśnienie związanego z tym wyrażenia regularnego, zapoznaj się z tym artykułem na blogu :)

Tajga
źródło
Nie jest konieczne wywołanie, obj.is_a? Stringponieważ ciąg # to_s zwróci się, co, jak sądzę, nie wymaga zbyt dużego przetwarzania w porównaniu z .is_a?wywołaniem. W ten sposób będziesz wykonywać tylko jedno połączenie w tej linii zamiast jednego lub dwóch. Można również umieścić bezpośrednio !!w number?metodzie, ponieważ zgodnie z konwencją nazwa metody kończąca się na ?ma zwracać wartość logiczną. Pozdrowienia!
Giovanni Benussi
-1

Podoba mi się następujące, proste:

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

Uważaj jednak:

is_integer?('123sdfsdf')
=> true
schmijos
źródło
-2

Jedna wkładka string.rb

def is_integer?; true if Integer(self) rescue false end
cb24
źródło
-3

Nie jestem pewien, czy tak było, gdy zadawano to pytanie, ale dla każdego, kto natknie się na ten post, najprostszym sposobem jest:

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? będzie działać z każdym przedmiotem.

Nowicjusz
źródło
1
Nie o to chodzi w pierwotnym pytaniu. OP chce wiedzieć, czy łańcuch jest również liczbą całkowitą. np. "12".is_an_integer? == true "not12".is_an_integer? == false 12.is_an_integer? == true
Marklar