अगर रूबी रेल्स में स्ट्रिंग एक नंबर है तो टेस्ट करें


103

मैं अपने आवेदन नियंत्रक में निम्नलिखित है:

def is_number?(object)
  true if Float(object) rescue false
end

और मेरे नियंत्रक में निम्नलिखित शर्त:

if mystring.is_number?

end

हालत एक undefined methodत्रुटि फेंक रहा है । मैं अनुमान लगा रहा हूँ कि मैंने is_numberगलत जगह पर परिभाषित किया है ...?


4
मुझे पता है कि बहुत सारे लोग यहां कोडक्यूलर रेल्स फॉर लाश टेस्टिंग क्लास के कारण हैं। बस उसे समझाते रहने का इंतजार करें। परीक्षणों को पास नहीं किया जाता है --- इसका ठीक है कि आपके पास परीक्षण त्रुटि में विफल है, आप हमेशा self.is_number जैसे तरीकों का आविष्कार करने के लिए रेल को पैच कर सकते हैं?
बोल्डर_रूबी

स्वीकृत उत्तर "1,000" जैसे मामलों पर विफल रहता है और रेक्सएक्स दृष्टिकोण का उपयोग करने की तुलना में 39x धीमा है। नीचे मेरा जवाब देखें।
पीथम

जवाबों:


186

is_number?विधि बनाएँ ।

एक सहायक विधि बनाएँ:

def is_number? string
  true if Float(string) rescue false
end

और फिर इसे इस तरह से कॉल करें:

my_string = '12.34'

is_number?( my_string )
# => true

बढ़ाएँ Stringकक्षा ।

यदि आप is_number?अपने सहायक कार्य के लिए इसे एक परम के रूप में पारित करने के बजाय सीधे तार पर कॉल करने में सक्षम होना चाहते हैं , तो आपको कक्षा के is_number?विस्तार के रूप में परिभाषित करने की आवश्यकता है String, जैसे:

class String
  def is_number?
    true if Float(self) rescue false
  end
end

और फिर आप इसे कॉल कर सकते हैं:

my_string.is_number?
# => true

2
यह विचार अच्छा नहीं है। "330.346.11" .to_f # => 330.346
एपोचॉल्फ

11
to_fइसके बाद के संस्करण में कोई नहीं है, और फ्लोट () उस व्यवहार को प्रदर्शित नहीं करता है: Float("330.346.11")उठाता हैArgumentError: invalid value for Float(): "330.346.11"
जैकब एस

7
यदि आप उस पैच का उपयोग करते हैं, तो मैं इसका नाम बदलकर सांख्यिक रख दूंगा ?, माणिक नामकरण परंपराओं के अनुरूप रहने के लिए (न्यूमेरिक से विरासत में मिली कक्षाएं, is_ उपसर्ग javaish हैं)।
कोनराड रेइच

10
मूल प्रश्न के लिए वास्तव में प्रासंगिक नहीं है, लेकिन मैं शायद कोड डालूँगा lib/core_ext/string.rb
जैकब एस

1
मुझे नहीं लगता कि is_number?(string)रूबी 1.9 का काम करती है। शायद यह रेल या 1.8 का हिस्सा है? String.is_a?(Numeric)काम करता है। यह भी देखें stackoverflow.com/questions/2095493/...
रॉस अट्रिल

30

इस समस्या को हल करने के सामान्य तरीकों के लिए एक बेंचमार्क है। ध्यान दें कि आपको जो उपयोग करना चाहिए, वह संभवतः झूठे मामलों के अनुपात पर निर्भर करता है।

  1. यदि वे अपेक्षाकृत असामान्य कास्टिंग हैं तो निश्चित रूप से सबसे तेज है।
  2. यदि झूठे मामले आम हैं और आप सिर्फ ints के लिए जाँच कर रहे हैं, तो एक परिवर्तित राज्य बनाम तुलना एक अच्छा विकल्प है।
  3. यदि झूठे मामले आम हैं और आप झांकियों की जांच कर रहे हैं, तो रेगेक्सप शायद जाने का रास्ता है

यदि प्रदर्शन से कोई फर्क नहीं पड़ता कि आपको क्या पसंद है। :-)

पूर्ण जाँच विवरण:

# 1.9.3-p448
#
# Calculating -------------------------------------
#                 cast     57485 i/100ms
#            cast fail      5549 i/100ms
#                 to_s     47509 i/100ms
#            to_s fail     50573 i/100ms
#               regexp     45187 i/100ms
#          regexp fail     42566 i/100ms
# -------------------------------------------------
#                 cast  2353703.4 (±4.9%) i/s -   11726940 in   4.998270s
#            cast fail    65590.2 (±4.6%) i/s -     327391 in   5.003511s
#                 to_s  1420892.0 (±6.8%) i/s -    7078841 in   5.011462s
#            to_s fail  1717948.8 (±6.0%) i/s -    8546837 in   4.998672s
#               regexp  1525729.9 (±7.0%) i/s -    7591416 in   5.007105s
#          regexp fail  1154461.1 (±5.5%) i/s -    5788976 in   5.035311s

require 'benchmark/ips'

int = '220000'
bad_int = '22.to.2'

Benchmark.ips do |x|
  x.report('cast') do
    Integer(int) rescue false
  end

  x.report('cast fail') do
    Integer(bad_int) rescue false
  end

  x.report('to_s') do
    int.to_i.to_s == int
  end

  x.report('to_s fail') do
    bad_int.to_i.to_s == bad_int
  end

  x.report('regexp') do
    int =~ /^\d+$/
  end

  x.report('regexp fail') do
    bad_int =~ /^\d+$/
  end
end

फ्लोट जाँच विवरण:

# 1.9.3-p448
#
# Calculating -------------------------------------
#                 cast     47430 i/100ms
#            cast fail      5023 i/100ms
#                 to_s     27435 i/100ms
#            to_s fail     29609 i/100ms
#               regexp     37620 i/100ms
#          regexp fail     32557 i/100ms
# -------------------------------------------------
#                 cast  2283762.5 (±6.8%) i/s -   11383200 in   5.012934s
#            cast fail    63108.8 (±6.7%) i/s -     316449 in   5.038518s
#                 to_s   593069.3 (±8.8%) i/s -    2962980 in   5.042459s
#            to_s fail   857217.1 (±10.0%) i/s -    4263696 in   5.033024s
#               regexp  1383194.8 (±6.7%) i/s -    6884460 in   5.008275s
#          regexp fail   723390.2 (±5.8%) i/s -    3613827 in   5.016494s

require 'benchmark/ips'

float = '12.2312'
bad_float = '22.to.2'

Benchmark.ips do |x|
  x.report('cast') do
    Float(float) rescue false
  end

  x.report('cast fail') do
    Float(bad_float) rescue false
  end

  x.report('to_s') do
    float.to_f.to_s == float
  end

  x.report('to_s fail') do
    bad_float.to_f.to_s == bad_float
  end

  x.report('regexp') do
    float =~ /^[-+]?[0-9]*\.?[0-9]+$/
  end

  x.report('regexp fail') do
    bad_float =~ /^[-+]?[0-9]*\.?[0-9]+$/
  end
end

29
class String
  def numeric?
    return true if self =~ /\A\d+\Z/
    true if Float(self) rescue false
  end
end  

p "1".numeric?  # => true
p "1.2".numeric? # => true
p "5.4e-29".numeric? # => true
p "12e20".numeric? # true
p "1a".numeric? # => false
p "1.2.3.4".numeric? # => false

12
/^\d+$/रूबी में एक सुरक्षित रेगेक्स नहीं है, /\A\d+\Z/है। (उदाहरण के लिए "42 \ nsome पाठ" वापस आएगा true)
टिमोथी ए

@ टिमोथेए की टिप्पणी पर स्पष्ट करने के लिए, /^\d+$/यदि लाइनों से निपटना उपयोग करना सुरक्षित है, लेकिन इस मामले में यह एक स्ट्रिंग की शुरुआत और अंत के बारे में है, इस प्रकार /\A\d+\Z/
जूलियो

1
क्या उत्तर को संपादित करने के लिए उत्तर नहीं दिया जाना चाहिए उत्तरदाता द्वारा? यदि आप प्रत्युत्तर नहीं दे रहे हैं, तो एक संपादन में उत्तर को बदलना ... संभवतः कम आंका गया और सीमा से बाहर होना चाहिए।
jaydel

2
\ Z स्ट्रिंग के अंत में \ n की अनुमति देता है, इसलिए "123 \ n" सत्यापन पास करेगा, भले ही यह पूरी तरह से संख्यात्मक न हो। लेकिन अगर आप \ z का उपयोग करते हैं तो यह अधिक सही होगा regexp: / \ A \ d + \ z /
SunnyMagadan

15

उठाए गए अपवाद पर भरोसा करना सबसे तेज़, पठनीय और विश्वसनीय समाधान नहीं है।
मैं निम्नलिखित कार्य करूंगा:

my_string.should =~ /^[0-9]+$/

1
हालांकि यह केवल सकारात्मक पूर्णांक के लिए काम करता है। मान जैसे '-1', '0.0', या '1_000' सभी वैध संख्यात्मक मान होते हुए भी झूठे हैं। आप कुछ ऐसा देख रहे हैं जैसे / ^ [- .0-9] + $ /, लेकिन यह गलती से '-' स्वीकार कर लेता है
जैकब एस

13
रेल से 'validates_numericality_of': raw_value.to_s = ~ / \ A [+]! \ D + \ Z /
मोर्टन

NoMethodError: अपरिभाषित विधि `चाहिए 'के लिए" asd ": स्ट्रिंग
सर्जगर्ल

नवीनतम rspec में, यह बन जाता हैexpect(my_string).to match(/^[0-9]+$/)
डेमियन MATHIEU

मुझे पसंद है: my_string =~ /\A-?(\d+)?\.?\d+\Z/यह आपको '.1', '-0.1', या '12' करने देता है, लेकिन '' या '' - '' ''।
जोश

8

रूबी 2.6.0 के रूप में, संख्यात्मक कलाकारों के तरीकों में एक वैकल्पिक exception-संक्रमण होता है [1] । यह हमें नियंत्रण प्रवाह के रूप में अपवादों का उपयोग किए बिना अंतर्निहित विधियों का उपयोग करने में सक्षम बनाता है:

Float('x') # => ArgumentError (invalid value for Float(): "x")
Float('x', exception: false) # => nil

इसलिए, आपको अपनी स्वयं की विधि को परिभाषित करने की आवश्यकता नहीं है, लेकिन सीधे जैसे चर की जांच कर सकते हैं

if Float(my_var, exception: false)
  # do something if my_var is a float
end

7

यह है कि मैं इसे कैसे करता हूं, लेकिन मुझे लगता है कि एक बेहतर तरीका होना चाहिए

object.to_i.to_s == object || object.to_f.to_s == object

5
यह फ्लोटिंग नोटेशन को नहीं पहचानता है, उदाहरण के लिए 1.2e + 35।
हिपर्ट्रैकर

1
रूबी में 2.4.0 मैं भागा object = "1.2e+35"; object.to_f.to_s == objectऔर यह काम किया
जियोवानी बेनुसी

6

नहीं, आप इसे गलत उपयोग कर रहे हैं। आपका is_number है? एक तर्क है। आपने इसे तर्क के बिना बुलाया

आप_नंबर कर रहे होंगे? (रहस्यमयी)


Is_number के आधार पर? सवाल में विधि, is_a का उपयोग कर रहा है? सही जवाब नहीं दे रहा है। यदि mystringवास्तव में एक स्ट्रिंग है, mystring.is_a?(Integer)तो हमेशा गलत होगा। ऐसा लगता है कि वह एक परिणाम चाहता है जैसेis_number?("12.4") #=> true
जैकब एस

जकोब एस सही है। मिस्ट्रिंग वास्तव में हमेशा एक स्ट्रिंग है, लेकिन इसमें केवल संख्याएँ शामिल हो सकती हैं। शायद मेरा सवाल is_numeric होना चाहिए था? ताकि डेटेटाइप को भ्रमित न किया जाए
जेमी बुकानन

6

Tl; dr: regex दृष्टिकोण का उपयोग करें। यह स्वीकृत उत्तर में बचाव के दृष्टिकोण से 39 गुना तेज है और "1,000" जैसे मामलों को भी संभालता है

def regex_is_number? string
  no_commas =  string.gsub(',', '')
  matches = no_commas.match(/-?\d+(?:\.\d+)?/)
  if !matches.nil? && matches.size == 1 && matches[0] == no_commas
    true
  else
    false
  end
end

-

@ जाकोब एस द्वारा स्वीकृत उत्तर अधिकांश भाग के लिए काम करता है, लेकिन अपवादों को पकड़ना वास्तव में धीमा हो सकता है। इसके अलावा, बचाव दृष्टिकोण "1,000" जैसे एक स्ट्रिंग पर विफल रहता है।

आइए तरीकों को परिभाषित करें:

def rescue_is_number? string
  true if Float(string) rescue false
end

def regex_is_number? string
  no_commas =  string.gsub(',', '')
  matches = no_commas.match(/-?\d+(?:\.\d+)?/)
  if !matches.nil? && matches.size == 1 && matches[0] == no_commas
    true
  else
    false
  end
end

और अब कुछ परीक्षण मामले:

test_cases = {
  true => ["5.5", "23", "-123", "1,234,123"],
  false => ["hello", "99designs", "(123)456-7890"]
}

और परीक्षण मामलों को चलाने के लिए एक छोटा कोड:

test_cases.each do |expected_answer, cases|
  cases.each do |test_case|
    if rescue_is_number?(test_case) != expected_answer
      puts "**rescue_is_number? got #{test_case} wrong**"
    else
      puts "rescue_is_number? got #{test_case} right"
    end

    if regex_is_number?(test_case) != expected_answer
      puts "**regex_is_number? got #{test_case} wrong**"
    else
      puts "regex_is_number? got #{test_case} right"
    end  
  end
end

यहाँ परीक्षण मामलों का उत्पादन होता है:

rescue_is_number? got 5.5 right
regex_is_number? got 5.5 right
rescue_is_number? got 23 right
regex_is_number? got 23 right
rescue_is_number? got -123 right
regex_is_number? got -123 right
**rescue_is_number? got 1,234,123 wrong**
regex_is_number? got 1,234,123 right
rescue_is_number? got hello right
regex_is_number? got hello right
rescue_is_number? got 99designs right
regex_is_number? got 99designs right
rescue_is_number? got (123)456-7890 right
regex_is_number? got (123)456-7890 right

कुछ प्रदर्शन बेंचमार्क करने का समय:

Benchmark.ips do |x|

  x.report("rescue") { test_cases.values.flatten.each { |c| rescue_is_number? c } }
  x.report("regex") { test_cases.values.flatten.each { |c| regex_is_number? c } }

  x.compare!
end

और परिणाम:

Calculating -------------------------------------
              rescue   128.000  i/100ms
               regex     4.649k i/100ms
-------------------------------------------------
              rescue      1.348k 16.8%) i/s -      6.656k
               regex     52.113k  7.8%) i/s -    260.344k

Comparison:
               regex:    52113.3 i/s
              rescue:     1347.5 i/s - 38.67x slower

बेंचमार्क के लिए धन्यवाद। स्वीकृत उत्तर में इनपुट की तरह स्वीकार करने का लाभ है 5.4e-29। मुझे लगता है कि आपके रेगेक्स को उन लोगों को भी स्वीकार करने के लिए ट्विक किया जा सकता है।
जोड़ी

3
1,000 जैसे मामलों को संभालना वास्तव में कठिन है, क्योंकि यह उपयोगकर्ता के इरादे पर निर्भर करता है। मनुष्यों के लिए संख्याओं को प्रारूपित करने के कई तरीके हैं। 1000 के बराबर 1,000 है, या 1 के बराबर है? दुनिया के अधिकांश लोगों का कहना है कि यह लगभग 1 है, पूर्णांक 1000 दिखाने का कोई तरीका नहीं है।
जेम्स मूर

4

रेल 4 में, आपको require File.expand_path('../../lib', __FILE__) + '/ext/string' अपने config / application.rb में डालना होगा


1
वास्तव में आपको ऐसा करने की आवश्यकता नहीं है, आप "initializers" में string.rb डाल सकते हैं और यह काम करता है!
महात्मनिच

3

यदि आप अपवाद के रूप में अपवादों का उपयोग नहीं करना चाहते हैं, तो आप यह कोशिश कर सकते हैं:

class String
   def numeric?
    !!(self =~ /^-?\d+(\.\d*)?$/)
  end
end

या, यदि आप यह सब वस्तु वर्गों में काम करना चाहते हैं, की जगह class Stringके साथ class Objectएक स्ट्रिंग के लिए एक परिवर्तित स्व: !!(self.to_s =~ /^-?\d+(\.\d*)?$/)


nil?व्हाट्सएप का उद्देश्य नकारात्मक है और शून्य करना माणिक पर दुखद है, इसलिए आप बस कर सकते हैं!!(self =~ /^-?\d+(\.\d*)?$/)
अर्नोल्ड रोआ

का उपयोग करना !!निश्चित रूप से काम करता है। कम से कम एक रूबी स्टाइल गाइड ( github.com/bbatsov/ruby-style-guide ) ने पठनीयता के !!पक्ष में परहेज करने का सुझाव दिया .nil?, लेकिन मैंने !!लोकप्रिय रिपॉजिटरी में इस्तेमाल किया है, और मुझे लगता है कि यह बूलियन में बदलने का एक अच्छा तरीका है। मैंने उत्तर संपादित किया है।
मार्क श्नाइडर

-3

निम्नलिखित फ़ंक्शन का उपयोग करें:

def is_numeric? val
    return val.try(:to_f).try(:to_s) == val
end

इसलिए,

is_numeric? "1.2f" = असत्य

is_numeric? "1.2" = सत्य

is_numeric? "12f" = असत्य

is_numeric? "12" = सत्य


यह विफल हो जाएगा अगर घाटी है "0"। यह भी ध्यान दें कि विधि .tryरूबी कोर लाइब्रेरी का हिस्सा नहीं है और केवल तभी उपलब्ध है जब आप ActiveSupport को शामिल कर रहे हैं।
GMA

वास्तव में, यह भी विफल रहता है "12", इसलिए इस प्रश्न में आपका चौथा उदाहरण गलत है। "12.10"और "12.00"असफल भी।
GMA

-5

यह समाधान कितना गूंगा है?

def is_number?(i)
  begin
    i+0 == i
  rescue TypeError
    false
  end
end

1
यह उप-इष्टतम है क्योंकि '.respond_to? (: +)' का उपयोग करना हमेशा बेहतर होता है, फिर किसी विशिष्ट विधि (: +) कॉल पर एक अपवाद को विफल करना और पकड़ना। यह भी कई कारणों से विफल हो सकता है रेगेक्स और रूपांतरण के तरीके नहीं थे।
साकची
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.