2011-11-09 17 views
5

Wierzę, że mam dobrą odpowiedź na to pytanie, ale chciałem się upewnić, że ruby-philes nie mają o wiele lepszego sposobu na zrobienie tego.Konwertuj wartość wejściową na liczbę całkowitą lub zmiennoprzecinkową, stosownie do Ruby

Zasadniczo, biorąc pod uwagę ciąg wejściowy, chciałbym przekonwertować ciąg na liczbę całkowitą, w stosownych przypadkach lub float, w stosownych przypadkach. W przeciwnym razie po prostu zwróć ciąg znaków.

Poniżej zamieszczam moją odpowiedź, ale chciałbym się dowiedzieć, czy jest lepszy sposób.

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" 

Odpowiedz

9

Unikałbym używania wyrażenia regularnego w miarę możliwości w Ruby. Jest bardzo powolny.

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) 

Bardziej kompleksowe odniesienia:

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

Stos regexes może być dobrym pomysłem, jeśli chcesz obsługiwać liczby w notacji naukowej (co String#to_f robi):

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 

Można mash zarówno to_f skrzynek do jednego regex, jeśli chcesz.

To oczywiście nie powiedzie się, gdy zostanie nakarmiony '3,14159' w języku lokalnym, który używa przecinka jako separatora dziesiętnego.

+0

W mojej funkcji, to faktycznie zostanie zwrócone jako ciąg znaków, ponieważ konwersja zmiennoprzecinkowa zakończy się niepowodzeniem i wygeneruje wyjątek, a następnie zostanie uruchomiona procedura ratowania się i powrót. – WattsInABox

+0

@ WattsInABox: Właśnie jesteś (pokazuje, jak bardzo używam 'Float'). Ale wciąż masz na uwadze zapis naukowy. –

+0

To jest tru, @muistooshort. Dzięki! – WattsInABox

2

zależności od wymagań bezpieczeństwa.

def to_f_or_i_or_s s 
    eval(s) rescue s 
end 
0

użyłem tej metody

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 ma konwertery, które to zrobić.

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

#523.49 
#29 
#"kittens" 

Jednak z jakiegoś powodu zamienia "00029" na zmienny.

Powiązane problemy