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.
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 + $ /
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:
classStringdefinteger?
[ # 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|returntrueifself =~ match_pattern
endreturnfalseendend
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.
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.
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) rescuenilif int
int.times {|i| p i}
end
lub nawet
int = Integer(str) rescuefalse
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
endrescue ArgumentError
puts "Not an int, doing something else"end
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.
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 integerend
+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.
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:
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:
/^(\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.
@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).
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ę:
defnumber?(obj)
obj = obj.to_s unless obj.is_a? String
/\A[+-]?\d+(\.[\d]+)?\z/.match(obj)
end
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!
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? == false12.is_an_integer? == true
Odpowiedzi:
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
źródło
/regexp/ === self
zamiast!!(self =~ /regexp/)
konstrukcji. Możesz użyć klasy znaków „\ d” zamiast[0-9]
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
źródło
self.to_i.to_s == self
zInteger self rescue false
?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ć10
jako drugi argumentInteger
, aby liczba była zawsze interpretowana jako podstawa 10.źródło
#to_i
są po prostu zbyt zepsute ze względu na ich pobłażliwość.Integer()
jest kanoniczne, ponieważ zInteger ()
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.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
źródło
"12".match(/^(\d)+$/) # true "1.2".match(/^(\d)+$/) # false "dfs2".match(/^(\d)+$/) # false "13422".match(/^(\d)+$/) # true
źródło
true
ifalse
aleMatchData
instancji inil
!!
lub użyj,present?
jeśli potrzebujesz boolean!!( "12".match /^(\d)+$/ )
lub"12".match(/^(\d)+$/).present?
(ten ostatni wymaga Rails /Ruby 2.6.0 umożliwia rzutowanie na liczbę całkowitą bez zgłaszania wyjątku i zwraca,
nil
jeśli rzutowanie się nie powiedzie. A ponieważ wnil
większości zachowuje się jakfalse
w 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
źródło
class String def integer? Integer(self) return true rescue ArgumentError return false end end
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?
."01"
i takie.źródło
integer?("a string")
ftl.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.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
Integer
jest również dobre, ale powróci0
doInteger nil
źródło
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?
źródło
Mógłby być o wiele prostszy sposób
/(\D+)/.match('1221').nil? #=> true /(\D+)/.match('1a221').nil? #=> false /(\D+)/.match('01221').nil? #=> true
źródło
def isint(str) return !!(str =~ /^[-+]?[1-9]([0-9]*)?$/) end
źródło
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.
źródło
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
źródło
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.
źródło
"12blah".to_i => 12
. Może to powodować problemy w dziwnych scenariuszach.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/ .IntegerRegex
aby 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ćtrue
lubfalse
.match
jest metodą na łańcuchu, która dopasowuje wystąpienia zgodnie z podanym wyrażeniem regularnym w argumencie i zwraca dopasowane wartości lubnil
.!!
konwertuje wynikmatch
metody na równoważną wartość logiczną.String
klasie 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.źródło
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
źródło
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 :)
źródło
obj.is_a? String
ponieważ 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!!
wnumber?
metodzie, ponieważ zgodnie z konwencją nazwa metody kończąca się na?
ma zwracać wartość logiczną. Pozdrowienia!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
źródło
Jedna wkładka
string.rb
def is_integer?; true if Integer(self) rescue false end
źródło
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.źródło
"12".is_an_integer? == true
"not12".is_an_integer? == false
12.is_an_integer? == true