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.



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  

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

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.


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

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

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


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.

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


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?

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

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

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.


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

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.


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.

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

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 :)


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

-2

Jedna wkładka string.rb

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

-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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.