2011-11-09 6 views
5

Creo que tengo una buena respuesta a este problema, pero quería asegurarme de que los ruby-philes no tuvieran una forma mucho mejor de hacerlo.Convertir valor de entrada a entero o flotar, según corresponda con Ruby

Básicamente, dada una cadena de entrada, me gustaría convertir la cadena a un número entero, cuando corresponda, o un flotador, cuando corresponda. De lo contrario, simplemente devuelve la cadena.

Voy a publicar mi respuesta a continuación, pero me gustaría saber si hay una mejor manera de salir.

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" 

Respuesta

9

Evitaría usar regex siempre que sea posible en Ruby. Es notoriamente lento.

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) 

Un punto de referencia más amplio:

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

Un montón de expresiones regulares podría ser una buena idea si usted quiere manejar números en notación científica (que String#to_f hace):

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 

Usted puede triturar ambos casos to_f en una expresión regular si lo desea.

Esto, por supuesto, fallará cuando se alimenta '3,14159' en una configuración regional que utiliza una coma como separador decimal.

+0

En mi función, eso realmente sería devuelto como una cadena, ya que la conversión flotante fallaría y arrojaría una excepción y luego el rescate se ejecutaría a_s y regresaría. – WattsInABox

+0

@WattsInABox: Tienes razón (que muestra lo mucho que usar 'Float'). Pero todavía tienes notación científica de la que preocuparte. –

+0

Esto es tru, @muistooshort. ¡Gracias! – WattsInABox

2

depende de los requisitos de seguridad.

def to_f_or_i_or_s s 
    eval(s) rescue s 
end 
0

que utilizan este método

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 tiene convertidores que hacen esto.

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

#523.49 
#29 
#"kittens" 

Sin embargo, por alguna razón, convierte "00029" en un flotador.