web-dev-qa-db-fra.com

Concaténation de chaînes en rubis

Je cherche un moyen plus élégant de concaténer des chaînes en Ruby.

J'ai la ligne suivante:

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

Y a-t-il une meilleure façon de faire cela?

Et d'ailleurs, quelle est la différence entre << et +?

323
dagda1

Vous pouvez le faire de plusieurs manières:

  1. Comme vous l'avez montré avec << mais ce n'est pas le habituel way
  2. Avec interpolation de chaîne

    source = "#{ROOT_DIR}/#{project}/App.config"
    
  3. avec +

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

La deuxième méthode semble être plus efficace en termes de mémoire/vitesse que ce que j'ai vu (non mesurée cependant). Les trois méthodes génèrent une erreur constante non initialisée lorsque ROOT_DIR est nul.

Lorsque vous traitez avec des noms de chemin, vous pouvez utiliser File.join pour éviter de vous embrouiller avec le séparateur de chemin.

En fin de compte, c'est une question de goût.

518
Keltia

L'opérateur + est le choix de concaténation normal et constitue probablement le moyen le plus rapide de concaténer des chaînes.

La différence entre + et << est que << modifie l'objet sur son côté gauche et que + ne le fait pas.

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"
89
Matt Burke

Si vous ne faites que concaténer des chemins, vous pouvez utiliser la méthode File.join de Ruby.

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

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

Utiliser << aka concat est beaucoup plus efficace que +=, car ce dernier crée un objet temporel et remplace le premier objet par le nouvel objet.

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

sortie:

____________________________________________________________
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)
21
Danny

Puisqu'il s'agit d'un chemin, j'utiliserais probablement un tableau et je participerais:

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

Voici un autre repère inspiré par this Gist . Il compare la concaténation (+), l'ajout (<<) et l'interpolation (#{}) des chaînes dynamiques et prédéfinies.

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

sortie:

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)

Conclusion: l'interpolation en IRM est lourde.

7
Adobe

Laissez-moi vous montrer toute mon expérience avec cela.

J'avais une requête qui renvoyait 32 000 enregistrements, pour chaque enregistrement, j'ai appelé une méthode permettant de formater cet enregistrement de base de données en une chaîne formatée, puis de la concaténer en une chaîne qui, à la fin de tout ce processus, deviendrait un fichier sur disque.

Mon problème était que, d’après le disque, aux alentours de 24k, le processus de concaténation de la chaîne a tourné mal.

Je faisais cela en utilisant l'opérateur '+' habituel.

Quand je suis passé au '<<, c'était comme par magie. Était vraiment rapide.

Donc, je me souvenais de mon époque - de 1998 - lorsque j’utilisais Java et que je concaténais String avec '+' et que je passais de String à StringBuffer (et maintenant, nous, développeurs Java, avons StringBuilder). 

Je crois que le processus de +/<< dans le monde Ruby est identique à celui de +/StringBuilder.append dans le monde Java. 

Le premier réaffecte l'objet entier en mémoire et le second pointe vers une nouvelle adresse.

6
Marcio Mangar

Je préférerais utiliser Pathname:

require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'

à propos de << et + de la documentation Ruby:

+: retourne un new String contenant other_str concaténé à str

<<: concatène l'objet donné en str. Si l'objet est un Fixnum compris entre 0 et 255, il est converti en caractère avant la concaténation.

la différence est donc dans ce qui devient le premier opérande (<< fait les changements à la place, + renvoie la nouvelle chaîne, donc la mémoire est plus lourde) et ce qui sera si le premier opérande est Fixnum (<< s'ajoutera comme s'il s'agissait d'un caractère avec un code égal à ce nombre , + provoquera une erreur)

6
tig

Concaténation dites-vous? Que diriez-vous de la méthode #concat alors?

a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object

En toute justice, concat a pour alias <<.

5
Boris Stitnicky

Voici d'autres moyens de le faire:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

Etc ... 

5
Imran Alavi

Vous pouvez utiliser l'opérateur + ou <<, mais en Ruby, la fonction .concat est la plus préférable, car elle est beaucoup plus rapide que les autres opérateurs. Vous pouvez l'utiliser comme.

source = "#{ROOT_DIR}/".concat(project.concat.("/App.config"))
3
Muhammad Zubair

Vous pouvez également utiliser % comme suit:

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

Cette approche fonctionne également avec le guillemet ' (unique).

1
Mark

Vous pouvez concaténer directement dans la définition de chaîne:

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

La situation est importante, par exemple:

# 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

Dans le premier exemple, la concaténation avec l'opérateur + ne mettra pas à jour l'objet output, mais dans le deuxième exemple, l'opérateur << mettra à jour l'objet output à chaque itération. Donc, pour le type de situation ci-dessus, << est préférable.

0
Affan Khan