Estoy buscando una forma más elegante de concatenar cuerdas en Ruby.
Tengo la siguiente línea:
source = "#{ROOT_DIR}/" << project << "/App.config"
¿Hay una mejor manera de hacer esto?
Y para el caso, ¿cuál es la diferencia entre <<
y +
?
Estoy buscando una forma más elegante de concatenar cuerdas en Ruby.
Tengo la siguiente línea:
source = "#{ROOT_DIR}/" << project << "/App.config"
¿Hay una mejor manera de hacer esto?
Y para el caso, ¿cuál es la diferencia entre <<
y +
?
Respuestas:
Puedes hacerlo de varias maneras:
<<
pero esa no es la forma habitualCon interpolación de cuerdas
source = "#{ROOT_DIR}/#{project}/App.config"
con +
source = "#{ROOT_DIR}/" + project + "/App.config"
El segundo método parece ser más eficiente en términos de memoria / velocidad de lo que he visto (aunque no medido). Los tres métodos arrojarán un error constante no inicializado cuando ROOT_DIR es nulo.
Cuando se trata de nombres de ruta, puede utilizar File.join
para evitar desordenar con el separador de nombre de ruta.
Al final, es cuestión de gustos.
El +
operador es la opción de concatenación normal, y es probablemente la forma más rápida de concatenar cadenas.
La diferencia entre +
y <<
es que <<
cambia el objeto en su lado izquierdo, y +
no lo hace.
irb(main):001:0> s = 'a'
=> "a"
irb(main):002:0> s + 'b'
=> "ab"
irb(main):003:0> s
=> "a"
irb(main):004:0> s << 'b'
=> "ab"
irb(main):005:0> s
=> "ab"
+
y <<
va a ser casi lo mismo. Si está lidiando con muchas cadenas, o muy grandes, entonces puede notar una diferencia. Me sorprendió lo similares que se comportaron. gist.github.com/2895311
5.times do ... end
bloque) para cada intérprete, terminaría con resultados más precisos. Mis pruebas han demostrado que la interpolación es el método más rápido en todos los intérpretes de Ruby. Hubiera esperado <<
ser el más rápido, pero es por eso que hacemos una referencia.
de http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/
Usar <<
aka concat
es mucho más eficiente que +=
, ya que este último crea un objeto temporal y anula el primer objeto con el nuevo objeto.
require 'benchmark'
N = 1000
BASIC_LENGTH = 10
5.times do |factor|
length = BASIC_LENGTH * (10 ** factor)
puts "_" * 60 + "\nLENGTH: #{length}"
Benchmark.bm(10, '+= VS <<') do |x|
concat_report = x.report("+=") do
str1 = ""
str2 = "s" * length
N.times { str1 += str2 }
end
modify_report = x.report("<<") do
str1 = "s"
str2 = "s" * length
N.times { str1 << str2 }
end
[concat_report / modify_report]
end
end
salida:
____________________________________________________________
LENGTH: 10
user system total real
+= 0.000000 0.000000 0.000000 ( 0.004671)
<< 0.000000 0.000000 0.000000 ( 0.000176)
+= VS << NaN NaN NaN ( 26.508796)
____________________________________________________________
LENGTH: 100
user system total real
+= 0.020000 0.000000 0.020000 ( 0.022995)
<< 0.000000 0.000000 0.000000 ( 0.000226)
+= VS << Inf NaN NaN (101.845829)
____________________________________________________________
LENGTH: 1000
user system total real
+= 0.270000 0.120000 0.390000 ( 0.390888)
<< 0.000000 0.000000 0.000000 ( 0.001730)
+= VS << Inf Inf NaN (225.920077)
____________________________________________________________
LENGTH: 10000
user system total real
+= 3.660000 1.570000 5.230000 ( 5.233861)
<< 0.000000 0.010000 0.010000 ( 0.015099)
+= VS << Inf 157.000000 NaN (346.629692)
____________________________________________________________
LENGTH: 100000
user system total real
+= 31.270000 16.990000 48.260000 ( 48.328511)
<< 0.050000 0.050000 0.100000 ( 0.105993)
+= VS << 625.400000 339.800000 NaN (455.961373)
Dado que esta es una ruta, probablemente usaría array y join:
source = [ROOT_DIR, project, 'App.config'] * '/'
Aquí hay otro punto de referencia inspirado en esta esencia . Compara la concatenación ( +
), el agregado ( <<
) y la interpolación ( #{}
) para cadenas dinámicas y predefinidas.
require 'benchmark'
# we will need the CAPTION and FORMAT constants:
include Benchmark
count = 100_000
puts "Dynamic strings"
Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
bm.report("concat") { count.times { 11.to_s + '/' + 12.to_s } }
bm.report("append") { count.times { 11.to_s << '/' << 12.to_s } }
bm.report("interp") { count.times { "#{11}/#{12}" } }
end
puts "\nPredefined strings"
s11 = "11"
s12 = "12"
Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
bm.report("concat") { count.times { s11 + '/' + s12 } }
bm.report("append") { count.times { s11 << '/' << s12 } }
bm.report("interp") { count.times { "#{s11}/#{s12}" } }
end
salida:
Dynamic strings
user system total real
concat 0.050000 0.000000 0.050000 ( 0.047770)
append 0.040000 0.000000 0.040000 ( 0.042724)
interp 0.050000 0.000000 0.050000 ( 0.051736)
Predefined strings
user system total real
concat 0.030000 0.000000 0.030000 ( 0.024888)
append 0.020000 0.000000 0.020000 ( 0.023373)
interp 3.160000 0.160000 3.320000 ( 3.311253)
Conclusión: la interpolación en MRI es pesada.
Prefiero usar Pathname:
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
sobre <<
y +
desde documentos de ruby:
+
: Devuelve una nueva cadena que contiene other_str concatenada a str
<<
: Concatena el objeto dado a str. Si el objeto es un Fixnum entre 0 y 255, se convierte en un carácter antes de la concatenación.
así que la diferencia está en lo que se convierte en el primer operando ( <<
realiza cambios en el lugar, +
devuelve una nueva cadena para que tenga más memoria) y qué ocurrirá si el primer operando es Fixnum ( <<
se agregará como si fuera un carácter con un código igual a ese número, +
aumentará error)
Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>
. Esto es por diseño, basado en el ejemplo de rubydoc. Parece que File.join es más seguro.
(Pathname(ROOT_DIR) + project + 'App.config').to_s
si desea devolver un objeto de cadena.
Déjame mostrarte toda mi experiencia con eso.
Tuve una consulta que devolvió 32k de registros, para cada registro llamé a un método para formatear ese registro de la base de datos en una cadena formateada y luego concatenarlo en una cadena que al final de todo este proceso se convertirá en un archivo en el disco.
Mi problema fue que, según el registro, alrededor de 24k, el proceso de concatenación de la cadena se convirtió en un dolor.
Estaba haciendo eso usando el operador regular '+'.
Cuando cambié a '<<' fue como magia. Fue muy rápido
Entonces, recordé mis viejos tiempos, algo así como 1998, cuando estaba usando Java y concatenando String usando '+' y cambié de String a StringBuffer (y ahora nosotros, el desarrollador de Java tenemos el StringBuilder).
Creo que el proceso de + / << en el mundo Ruby es el mismo que + / StringBuilder.append en el mundo Java.
El primero reasigna todo el objeto en la memoria y el otro solo apunta a una nueva dirección.
Concatenación que dices? ¿Qué tal el #concat
método entonces?
a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object
Para ser justos, concat
tiene alias como <<
.
"foo" "bar" 'baz" #=> "foobarabaz"
Aquí hay más formas de hacer esto:
"String1" + "String2"
"#{String1} #{String2}"
String1<<String2
Y así ...
Puede usar +
u <<
operador, pero en la .concat
función ruby es la más preferible, ya que es mucho más rápido que otros operadores. Puedes usarlo como.
source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
.
después de tu último concat
no?
La situación importa, por ejemplo:
# this will not work
output = ''
Users.all.each do |user|
output + "#{user.email}\n"
end
# the output will be ''
puts output
# this will do the job
output = ''
Users.all.each do |user|
output << "#{user.email}\n"
end
# will get the desired output
puts output
En el primer ejemplo, la concatenación con el +
operador no actualizará el output
objeto, sin embargo, en el segundo ejemplo, el <<
operador actualizará el output
objeto con cada iteración. Entonces, para el tipo de situación anterior, <<
es mejor.
Para su caso particular, también podría usarlo Array#join
al construir el tipo de ruta de archivo de la cadena:
string = [ROOT_DIR, project, 'App.config'].join('/')]
Esto tiene un efecto secundario agradable de convertir automáticamente diferentes tipos a cadenas:
['foo', :bar, 1].join('/')
=>"foo/bar/1"