2011-11-09 23 views
5

Tôi tin rằng tôi có một câu trả lời tốt cho vấn đề này, nhưng tôi muốn chắc chắn rằng ruby-philes không có cách nào tốt hơn để làm điều này.Chuyển đổi giá trị đầu vào thành Integer hoặc Float, như là thích hợp khi sử dụng Ruby

Về cơ bản, được đưa ra một chuỗi đầu vào, tôi muốn chuyển đổi chuỗi thành số nguyên, nếu thích hợp hoặc một phao, nếu thích hợp. Nếu không, chỉ cần trả về chuỗi.

Tôi sẽ đăng câu trả lời của mình dưới đây, nhưng tôi muốn biết liệu có cách nào tốt hơn không.

Ex:

to_f_or_i_or_s("0523.49") #=> 523.49 
to_f_or_i_or_s("0000029") #=> 29 
to_f_or_i_or_s("kittens") #=> "kittens" 

Trả lời

9

Tôi sẽ tránh sử dụng regex bất cứ khi nào có thể trong Ruby. Nó nổi tiếng chậm.

def to_f_or_i_or_s(v) 
    ((float = Float(v)) && (float % 1.0 == 0) ? float.to_i : float) rescue v 
end 

# Proof of Ruby's slow regex 
def regex_float_detection(input) 
    input.match('\.') 
end 

def math_float_detection(input) 
    input % 1.0 == 0 
end 

n = 100_000 
Benchmark.bm(30) do |x| 
    x.report("Regex") { n.times { regex_float_detection("1.1") } } 
    x.report("Math") { n.times { math_float_detection(1.1) } } 
end 

#          user  system  total  real 
# Regex       0.180000 0.000000 0.180000 ( 0.181268) 
# Math       0.050000 0.000000 0.050000 ( 0.048692) 

Một benchmark toàn diện hơn:

def wattsinabox(input) 
    input.match('\.').nil? ? Integer(input) : Float(input) rescue input.to_s 
end 

def jaredonline(input) 
    ((float = Float(input)) && (float % 1.0 == 0) ? float.to_i : float) rescue input 
end 

def muistooshort(input) 
    case(input) 
    when /\A\s*[+-]?\d+\.\d+\z/ 
     input.to_f 
    when /\A\s*[+-]?\d+(\.\d+)?[eE]\d+\z/ 
     input.to_f 
    when /\A\s*[+-]?\d+\z/ 
     input.to_i  
    else 
     input 
    end 
end 

n = 1_000_000 
Benchmark.bm(30) do |x| 
    x.report("wattsinabox") { n.times { wattsinabox("1.1") } } 
    x.report("jaredonline") { n.times { jaredonline("1.1") } } 
    x.report("muistooshort") { n.times { muistooshort("1.1") } } 
end 

#          user  system  total  real 
# wattsinabox      3.600000 0.020000 3.620000 ( 3.647055) 
# jaredonline      1.400000 0.000000 1.400000 ( 1.413660) 
# muistooshort     2.790000 0.010000 2.800000 ( 2.803939) 
5
def to_f_or_i_or_s(v) 
    v.match('\.').nil? ? Integer(v) : Float(v) rescue v.to_s 
end 
2

Một đống regexes có thể là một ý tưởng tốt nếu bạn muốn xử lý số trong ký hiệu khoa học (mà String#to_f không):

def to_f_or_i_or_s(v) 
    case(v) 
    when /\A\s*[+-]?\d+\.\d+\z/ 
     v.to_f 
    when /\A\s*[+-]?\d+(\.\d+)?[eE]\d+\z/ 
     v.to_f 
    when /\A\s*[+-]?\d+\z/ 
     v.to_i  
    else 
     v 
    end 
end 

Bạn có thể nghiền nát cả hai trường hợp to_f vào một regex nếu bạn muốn.

Điều này tất nhiên sẽ không thành công khi được cho ăn '3,14159' bằng ngôn ngữ sử dụng dấu phẩy làm dấu phân cách thập phân.

+0

Trong chức năng của tôi, điều đó thực sự sẽ được trả về dưới dạng một chuỗi, vì chuyển đổi float sẽ thất bại và ném một ngoại lệ và sau đó giải cứu sẽ chạy to_s và trả về. – WattsInABox

+0

@WattsInABox: Đúng là bạn (cho bạn biết tôi sử dụng 'Float ') bao nhiêu. Nhưng bạn vẫn có ký hiệu khoa học để lo lắng. –

+0

Đây là tru, @muistooshort. Cảm ơn! – WattsInABox

2

Phụ thuộc vào yêu cầu bảo mật.

def to_f_or_i_or_s s 
    eval(s) rescue s 
end 
0

tôi đã sử dụng phương pháp này

def to_f_or_i_or_s(value) 
    return value if value[/[a-zA-Z]/] 

    i = value.to_i 
    f = value.to_f 

    i == f ? i : f 
    end 
0

CSV có bộ chuyển đổi mà làm điều này.

require "csv" 
strings = ["0523.49", "29","kittens"] 
strings.each{|s|p s.parse_csv(converters: :numeric).first} 

#523.49 
#29 
#"kittens" 

Tuy nhiên vì lý do nào đó, chuyển đổi "00029" thành phao.

Các vấn đề liên quan