Concatenación de cuerdas en Ruby


364

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 +?


3
Esta pregunta stackoverflow.com/questions/4684446/… está altamente relacionada.
Ojo

<< esta es una forma más eficiente de hacer concatenación.
Taimoor Changaiz

Respuestas:


575

Puedes hacerlo de varias maneras:

  1. Como mostraste con, <<pero esa no es la forma habitual
  2. Con interpolación de cuerdas

    source = "#{ROOT_DIR}/#{project}/App.config"
  3. 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.joinpara evitar desordenar con el separador de nombre de ruta.

Al final, es cuestión de gustos.


77
No tengo mucha experiencia con el rubí. Pero, en general, en los casos en que concatena muchas cadenas, a menudo puede obtener rendimiento agregando las cadenas a una matriz y luego al final, junte la cadena atómicamente. Entonces << podría ser útil?
PEZ

1
Tendrá que agregar memoria y copiar la cadena más larga de todos modos. << es más o menos lo mismo que + excepto que puedes << con un solo carácter.
Keltia

99
En lugar de usar << en los elementos de una matriz, use Array # join, es mucho más rápido.
Grant Hutchins

94

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"

32
El operador + definitivamente no es la forma más rápida de concatenar cadenas. Cada vez que lo usa, hace una copia, mientras que << se concatena en su lugar y es mucho más eficaz.
Evil Trout

55
Para la mayoría de los usos, la interpolación +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
Matt Burke

8
Los resultados de jruby están sesgados contra la interpolación por la sobrecarga de JVM de ejecución temprana. Si ejecuta el conjunto de pruebas varias veces (en el mismo proceso, por lo tanto, envuelva todo en digamos un 5.times do ... endbloque) 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.
womble

Sin ser demasiado versado en Ruby, tengo curiosidad por saber si la mutación se realiza en la pila o en el montón. Si está en el montón, incluso una operación de mutación, que parece ser más rápida, probablemente implique algún tipo de malloc. Sin él, esperaría un desbordamiento del búfer. Usar la pila podría ser bastante rápido, pero el valor resultante probablemente se coloca en el montón de todos modos, lo que requiere una operación malloc. Al final, espero que el puntero de la memoria sea una nueva dirección, incluso si la referencia variable hace que parezca una mutación in situ. Entonces, realmente, ¿hay alguna diferencia?
Robin Coe

79

Si solo está concatenando rutas, puede usar el método File.join de Ruby.

source = File.join(ROOT_DIR, project, 'App.config')

55
Este parece ser el camino a seguir, ya que Ruby se encargará de crear la cadena correcta en el sistema con diferentes separadores de ruta.
PEZ

26

de http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/

Usar <<aka concates 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)

11

Dado que esta es una ruta, probablemente usaría array y join:

source = [ROOT_DIR, project, 'App.config'] * '/'

9

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.


Como las cadenas comienzan a ser inmutables ahora, me encantaría ver un nuevo punto de referencia para esto.
bibstha

7

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)


2
Acabo de descubrir que llamar '+' en un nombre de ruta puede ser peligroso ya que si el arg es una ruta absoluta, el camino receptor se ignora: 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.
Kelvin

También debe llamar (Pathname(ROOT_DIR) + project + 'App.config').to_ssi desea devolver un objeto de cadena.
lacostenycoder

6

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.


5

Concatenación que dices? ¿Qué tal el #concatmé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, concattiene alias como <<.


77
Hay una forma más de pegar cuerdas juntas no mencionadas por otros, y es por mera yuxtaposición:"foo" "bar" 'baz" #=> "foobarabaz"
Boris Stitnicky

Nota para los demás: se supone que no es una cita simple, sino una doble como el resto. Método ordenado!
Joshua Pinter

5

Aquí hay más formas de hacer esto:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

Y así ...


2

También puede usar %lo siguiente:

source = "#{ROOT_DIR}/%s/App.config" % project

Este enfoque también funciona con 'comillas (simples).


2

Puede usar +u <<operador, pero en la .concatfunció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"))

Creo que tienes un extra .después de tu último concatno?
lacostenycoder

1

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 outputobjeto, sin embargo, en el segundo ejemplo, el <<operador actualizará el outputobjeto con cada iteración. Entonces, para el tipo de situación anterior, <<es mejor.


1

Puede concatenar en la definición de cadena directamente:

nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"

0

Para su caso particular, también podría usarlo Array#joinal 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"

0

Para títeres:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.